edsger 0.73.0 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/api/adr.d.ts +48 -0
  2. package/dist/api/adr.js +139 -0
  3. package/dist/commands/adr/index.d.ts +13 -0
  4. package/dist/commands/adr/index.js +31 -0
  5. package/dist/commands/features/index.d.ts +15 -0
  6. package/dist/commands/features/index.js +34 -0
  7. package/dist/commands/pr-resolve/index.d.ts +3 -1
  8. package/dist/commands/pr-resolve/index.js +12 -7
  9. package/dist/commands/pr-review/index.d.ts +3 -1
  10. package/dist/commands/pr-review/index.js +10 -6
  11. package/dist/commands/sync-github-pull-requests/index.d.ts +11 -0
  12. package/dist/commands/sync-github-pull-requests/index.js +42 -0
  13. package/dist/index.js +66 -4
  14. package/dist/phases/adr-generation/agent.d.ts +6 -0
  15. package/dist/phases/adr-generation/agent.js +69 -0
  16. package/dist/phases/adr-generation/index.d.ts +15 -0
  17. package/dist/phases/adr-generation/index.js +66 -0
  18. package/dist/phases/adr-generation/parse.d.ts +12 -0
  19. package/dist/phases/adr-generation/parse.js +123 -0
  20. package/dist/phases/adr-generation/prompts.d.ts +8 -0
  21. package/dist/phases/adr-generation/prompts.js +35 -0
  22. package/dist/phases/data-flow/mcp-server.d.ts +1 -1
  23. package/dist/phases/features/index.d.ts +65 -0
  24. package/dist/phases/features/index.js +292 -0
  25. package/dist/phases/features/mcp-server.d.ts +61 -0
  26. package/dist/phases/features/mcp-server.js +165 -0
  27. package/dist/phases/features/prompts.d.ts +32 -0
  28. package/dist/phases/features/prompts.js +92 -0
  29. package/dist/phases/features/types.d.ts +34 -0
  30. package/dist/phases/features/types.js +15 -0
  31. package/dist/phases/pr-resolve/index.d.ts +3 -1
  32. package/dist/phases/pr-resolve/index.js +12 -12
  33. package/dist/phases/pr-review/index.d.ts +3 -1
  34. package/dist/phases/pr-review/index.js +13 -16
  35. package/dist/phases/pr-shared/status.d.ts +18 -0
  36. package/dist/phases/pr-shared/status.js +37 -0
  37. package/dist/phases/quality-benchmark/parsers.js +79 -0
  38. package/dist/phases/quality-benchmark/rubric.md +125 -17
  39. package/dist/phases/quality-benchmark/tool-catalog.js +39 -0
  40. package/dist/phases/sync-github-pull-requests/index.d.ts +23 -0
  41. package/dist/phases/sync-github-pull-requests/index.js +210 -0
  42. package/dist/phases/sync-github-pull-requests/state.d.ts +24 -0
  43. package/dist/phases/sync-github-pull-requests/state.js +16 -0
  44. package/dist/phases/sync-github-pull-requests/types.d.ts +22 -0
  45. package/dist/phases/sync-github-pull-requests/types.js +1 -0
  46. package/dist/skills/phase/adr-generation/SKILL.md +51 -0
  47. package/package.json +1 -1
@@ -301,6 +301,31 @@ const cargoOutdatedParser = (stdout) => {
301
301
  oneliner: `${count} outdated crates`,
302
302
  };
303
303
  };
304
+ const dotnetListOutdatedParser = (stdout) => {
305
+ // `dotnet list package --outdated --format json` →
306
+ // { projects: [{ frameworks: [{ topLevelPackages: [{ resolvedVersion, latestVersion }] }] }] }
307
+ const data = safeJson(stdout);
308
+ let count = 0;
309
+ for (const proj of data?.projects ?? []) {
310
+ for (const fw of proj.frameworks ?? []) {
311
+ for (const pkg of fw.topLevelPackages ?? []) {
312
+ if (pkg.latestVersion &&
313
+ pkg.resolvedVersion &&
314
+ pkg.latestVersion !== pkg.resolvedVersion) {
315
+ count++;
316
+ }
317
+ }
318
+ }
319
+ }
320
+ return {
321
+ tool_id: 'dotnet-list-outdated',
322
+ summary: {
323
+ tier: 'counts',
324
+ counts: { errors: 0, warnings: count, info: 0 },
325
+ },
326
+ oneliner: `${count} outdated NuGet packages`,
327
+ };
328
+ };
304
329
  // ---------------------------------------------------------------------------
305
330
  // Tier 2 — counts + top-N findings (severity + file:line preserved)
306
331
  // ---------------------------------------------------------------------------
@@ -701,6 +726,58 @@ const bundlerAuditParser = (stdout) => {
701
726
  oneliner: `${findings.length} Ruby gem vulnerabilities`,
702
727
  };
703
728
  };
729
+ function mapDotnetSeverity(s) {
730
+ switch ((s ?? '').toLowerCase()) {
731
+ case 'critical':
732
+ return 'critical';
733
+ case 'high':
734
+ return 'high';
735
+ case 'moderate':
736
+ return 'medium';
737
+ case 'low':
738
+ return 'low';
739
+ default:
740
+ return 'medium';
741
+ }
742
+ }
743
+ const dotnetListVulnerableParser = (stdout, _stderr, ctx) => {
744
+ // `dotnet list package --vulnerable --include-transitive --format json` →
745
+ // { projects: [{ path, frameworks: [{ topLevelPackages, transitivePackages }] }] }
746
+ // where each package has { id, resolvedVersion, vulnerabilities: [{ severity, advisoryurl }] }
747
+ const data = safeJson(stdout);
748
+ const findings = [];
749
+ for (const proj of data?.projects ?? []) {
750
+ const file = relPath(proj.path ?? 'csproj', ctx);
751
+ for (const fw of proj.frameworks ?? []) {
752
+ const pkgs = [
753
+ ...(fw.topLevelPackages ?? []),
754
+ ...(fw.transitivePackages ?? []),
755
+ ];
756
+ for (const pkg of pkgs) {
757
+ for (const v of pkg.vulnerabilities ?? []) {
758
+ findings.push({
759
+ file,
760
+ line: 1,
761
+ issue: `${pkg.id ?? 'package'}@${pkg.resolvedVersion ?? '?'}: ${v.advisoryurl ?? 'known vulnerability'}`,
762
+ severity: mapDotnetSeverity(v.severity),
763
+ source: 'tool:dotnet-list-vulnerable',
764
+ rule_id: v.advisoryurl,
765
+ subscore_key: primarySubscore('dotnet-list-vulnerable'),
766
+ });
767
+ }
768
+ }
769
+ }
770
+ }
771
+ return {
772
+ tool_id: 'dotnet-list-vulnerable',
773
+ summary: {
774
+ tier: 'findings',
775
+ counts: countBySeverity(findings),
776
+ top_findings: topBySeverity(findings),
777
+ },
778
+ oneliner: `${findings.length} vulnerable NuGet packages`,
779
+ };
780
+ };
704
781
  const vultureParser = (stdout, _stderr, ctx) => {
705
782
  // vulture text output: `path:line: unused X 'name' (confidence%)`
706
783
  const findings = [];
@@ -973,6 +1050,7 @@ export const PARSERS = {
973
1050
  'npm-outdated': npmOutdatedParser,
974
1051
  'go-mod-outdated': goModOutdatedParser,
975
1052
  'cargo-outdated': cargoOutdatedParser,
1053
+ 'dotnet-list-outdated': dotnetListOutdatedParser,
976
1054
  // Tier 2
977
1055
  semgrep: semgrepParser,
978
1056
  bandit: banditParser,
@@ -988,6 +1066,7 @@ export const PARSERS = {
988
1066
  'cargo-deny': cargoDenyParser,
989
1067
  'osv-scanner': osvScannerParser,
990
1068
  'bundler-audit': bundlerAuditParser,
1069
+ 'dotnet-list-vulnerable': dotnetListVulnerableParser,
991
1070
  vulture: vultureParser,
992
1071
  madge: madgeParser,
993
1072
  gocyclo: gocycloParser,
@@ -11,6 +11,7 @@
11
11
  You are a **senior software architect** running an industrial-grade quality benchmark on a code repository. You orchestrate a suite of static analysis tools (ESLint, Semgrep, Pylint, clippy, etc.) and external signals (GitHub Issues, Sentry, git history), then synthesize their outputs into a structured, evidence-based report.
12
12
 
13
13
  You have:
14
+
14
15
  - `Read` / `Grep` / `Glob` — source inspection
15
16
  - `Bash` — running tools, probing the environment, executing installation commands
16
17
  - MCP tools where available (GitHub Issues via `list_issues`, Sentry via Sentry MCP if configured)
@@ -24,7 +25,7 @@ You produce a single JSON report at the end of your response.
24
25
  - **Layer 1 (FIXED — never deviate)**: the 8 dimensions, their scoring anchors (90+/75–89/…), the A–F mapping, the N/A rule, the **Unmeasured rule** (new), the evidence/recommendation formats, the overall-score formula.
25
26
  - **Layer 2 (ADAPTIVE — you decide per repo)**: the specific checks you run inside each dimension and the specific tools you invoke. Layer 2 is constrained by the **Tool Catalog** below — you may only run commands listed in the catalog (with the documented flags), not commands you invent.
26
27
 
27
- You **must not** invent new dimensions, change scoring anchors, change weights, or run tool commands not in the catalog. You **must** adapt the *selection* of catalog commands to the detected repo.
28
+ You **must not** invent new dimensions, change scoring anchors, change weights, or run tool commands not in the catalog. You **must** adapt the _selection_ of catalog commands to the detected repo.
28
29
 
29
30
  ---
30
31
 
@@ -45,12 +46,14 @@ Do not skip phases. Do not score before tools have run.
45
46
  ### Phase 1 — Detection
46
47
 
47
48
  Read at minimum:
49
+
48
50
  - `README*`, top-level `ls`
49
51
  - Manifests: `package.json`, `pyproject.toml`, `requirements.txt`, `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `Gemfile`, `composer.json`, `mix.exs`, etc.
50
52
  - CI: `.github/workflows/`, `.gitlab-ci.yml`, `.circleci/`
51
53
  - Lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `poetry.lock`, `Cargo.lock`, `go.sum`
52
54
 
53
55
  Determine:
56
+
54
57
  - **`archetype`**: `library` | `cli` | `web-app` | `backend-service` | `mobile` | `data-pipeline` | `infra` | `monorepo` | `embedded` | `desktop-app` | `other`
55
58
  - **`primary_languages`**: top 1–3 by LOC
56
59
  - **`frameworks`**: React/Next/Vue/Django/FastAPI/Rails/Spring/etc.
@@ -73,10 +76,12 @@ command -v <tool> # returns path or empty
73
76
  ```
74
77
 
75
78
  Record into:
79
+
76
80
  - `tool_versions` — `{ "<tool>": "<version>", ... }` for tools that ARE present
77
81
  - `unavailable_tools` — `[ { "name", "category", "install_command", "reason": "not_found" | "wrong_version" } ]`
78
82
 
79
83
  **Toolchain prerequisites** (Python, Node, Go, Rust, Ruby toolchains themselves):
84
+
80
85
  - Probe with `command -v python3`, `command -v node`, `command -v go`, `command -v cargo`, `command -v ruby`
81
86
  - If the toolchain for a detected language is missing, **do not attempt to install it**. Mark every check requiring that toolchain as unmeasured. Add a top-level warning in `executive_summary`.
82
87
 
@@ -93,6 +98,7 @@ Otherwise, for each tool in `unavailable_tools` with a known install method:
93
98
  5. On failure: leave in `unavailable_tools` with `reason: "install_failed"` + stderr tail (≤ 500 chars)
94
99
 
95
100
  **Hard rules**:
101
+
96
102
  - Never run `sudo`
97
103
  - Never run `apt`, `yum`, `brew`, `dnf`, `pacman`, or any system package manager
98
104
  - Never modify shell rc files (`.bashrc`, `.zshrc`)
@@ -110,11 +116,13 @@ For each available tool in the catalog that applies to the detected context:
110
116
  5. Each tool has a per-tool timeout (default 5 min, see catalog for overrides)
111
117
 
112
118
  **Per-tool failure handling**:
119
+
113
120
  - `exit_code != 0` but stdout has expected JSON → parse anyway (many linters exit non-zero on findings)
114
121
  - `exit_code != 0` with no parseable output → mark this tool's checks as unmeasured, log stderr
115
122
  - Timeout → mark as unmeasured, log "timed out after Xm"
116
123
 
117
124
  **Hard rules**:
125
+
118
126
  - Never run a tool with `--fix`, `--auto-fix`, or any flag that mutates files
119
127
  - Never run tools outside the catalog
120
128
  - Never modify the repo (no commits, no file edits)
@@ -125,23 +133,27 @@ For each available tool in the catalog that applies to the detected context:
125
133
  For each signal source, attempt to collect if available:
126
134
 
127
135
  **Git history** (always available):
136
+
128
137
  - `git log --since="90 days ago" --format="%an"` → unique author count → bus factor
129
138
  - `git log --since="30 days ago" --format=""` `--name-only` → file churn ranking
130
139
  - `git log --since="30 days ago" --format="%s" | head -50` → commit message quality sample
131
140
  - `git log --grep="fix\|bug\|hotfix" --since="90 days ago" --oneline | wc -l` → recent bug fix volume
132
141
 
133
142
  **GitHub Issues** (if `list_issues` MCP tool is available and product has `github_repository_full_name`):
143
+
134
144
  - Open issues by label (especially `bug`, `security`, `regression`)
135
145
  - Open security advisories count (if exposable)
136
146
  - Average issue age
137
147
  - Stale PR count (>30 days no update)
138
148
 
139
149
  **Sentry** (if Sentry MCP is configured for this product):
150
+
140
151
  - Unresolved error count last 7 days
141
152
  - Top 5 errors by frequency (title + count + first/last seen)
142
153
  - Error categories (uncaught, network, performance)
143
154
 
144
155
  Record into `external_signals`. These are **supplementary evidence**, not their own dimension. Cite them inside dimensions where they're relevant:
156
+
145
157
  - `external_signals.sentry` → Performance / Security evidence
146
158
  - `external_signals.github_issues` → Maintainability / Security evidence
147
159
  - `external_signals.git` → Maintainability evidence (churn) / Documentation evidence (commit messages)
@@ -159,6 +171,7 @@ Before scoring, walk every `evidence` entry produced by tool parsers:
159
171
  For each failure → discard the finding, increment `dropped_findings` counter, log to debug.
160
172
 
161
173
  Then **deduplicate**:
174
+
162
175
  - Same `file:line` reported by multiple tools (e.g. ESLint + Semgrep) → merge into single entry with `sources: ["eslint","semgrep"]`
163
176
  - Use the more severe `severity` and most specific `issue` text
164
177
 
@@ -182,13 +195,13 @@ For each of the 8 dimensions:
182
195
 
183
196
  ## Scoring anchors (FIXED — same for every dimension, every repo)
184
197
 
185
- | Score | Grade | Meaning |
186
- |-------|-------|---------|
198
+ | Score | Grade | Meaning |
199
+ | ------ | ----- | ----------------------------------------------------------------------- |
187
200
  | 90–100 | **A** | Exemplary. Top-tier engineering org review would surface only nitpicks. |
188
- | 75–89 | **B** | Solid. Minor issues, no structural concerns. |
189
- | 60–74 | **C** | Adequate. Several real issues, none critical. Needs attention. |
190
- | 40–59 | **D** | Poor. Significant remediation needed before production-grade. |
191
- | 0–39 | **F** | Critical. Issues threaten correctness, security, or maintainability. |
201
+ | 75–89 | **B** | Solid. Minor issues, no structural concerns. |
202
+ | 60–74 | **C** | Adequate. Several real issues, none critical. Needs attention. |
203
+ | 40–59 | **D** | Poor. Significant remediation needed before production-grade. |
204
+ | 0–39 | **F** | Critical. Issues threaten correctness, security, or maintainability. |
192
205
 
193
206
  **Be honest, not generous.** Do not adjust for stage, domain, or excuses.
194
207
 
@@ -197,6 +210,7 @@ For each of the 8 dimensions:
197
210
  ## Unmeasured rule (NEW — critical)
198
211
 
199
212
  **Unmeasured ≠ N/A.** A check is "unmeasured" when:
213
+
200
214
  - The required tool was unavailable and could not be installed
201
215
  - The required tool errored out
202
216
  - The required toolchain (e.g. Go) was missing
@@ -204,6 +218,7 @@ For each of the 8 dimensions:
204
218
  An unmeasured check contributes **0** to its parent subscore's weighted average. The rationale: in industrial-grade benchmarking, lack of validation is itself a risk signal — "we don't know if it's secure" is not the same as "it's secure".
205
219
 
206
220
  **Per-subscore reporting**:
221
+
207
222
  - `measured_coverage`: percentage of weighted checks that ran (0–1)
208
223
  - If `measured_coverage < 0.5` for any subscore, append to its `summary`: `"Limited measurement (X% checks ran) — install missing tools for accurate score."`
209
224
  - Surface unmeasured checks as recommendations: `{ "title": "Install semgrep to enable SAST measurement", "effort": "low", "impact": "high", ... }`
@@ -217,15 +232,17 @@ An unmeasured check contributes **0** to its parent subscore's weighted average.
217
232
  A dimension is N/A **only when structurally inapplicable** — not when it just scores low or can't be measured.
218
233
 
219
234
  Valid N/A:
235
+
220
236
  - "Performance" on a single-file shell-script CLI with no hot path
221
237
  - "Test Coverage" on a doc-only repo with no executable code
222
238
  - "Dependency Health" on a repo with zero external dependencies
223
239
 
224
240
  Invalid N/A (must score, not skip):
241
+
225
242
  - "Documentation" on a repo with poor docs — that's a low score
226
243
  - "Security" on internal tool — security still applies
227
244
  - "Test Coverage" on an MVP — score it low
228
- - "Code Quality" because no linter is installed — that's *unmeasured*, not N/A
245
+ - "Code Quality" because no linter is installed — that's _unmeasured_, not N/A
229
246
 
230
247
  Set `score: null`, `grade: "N/A"`, provide `n_a_reason`. Excluded from `overall_score`.
231
248
 
@@ -266,6 +283,7 @@ This prevents one catastrophic issue from being averaged away.
266
283
  ## The 8 dimensions
267
284
 
268
285
  For each dimension, the rubric specifies:
286
+
269
287
  - **Measures** (what the dimension means)
270
288
  - **Tool-driven checks** (catalog tools that contribute, with their subscore mapping)
271
289
  - **LLM-judgment checks** (what you add by reading code)
@@ -277,12 +295,14 @@ For each dimension, the rubric specifies:
277
295
  **Measures**: organization into cohesive, loosely coupled modules with clear boundaries.
278
296
 
279
297
  **Tool-driven checks**:
298
+
280
299
  - `madge --circular` (JS/TS) → `arch.circular_deps`
281
300
  - `dependency-cruiser --validate` (JS/TS, if configured) → `arch.layering`
282
301
  - `pydeps --show-cycles` (Python) → `arch.circular_deps`
283
302
  - `go list -deps` analysis (Go) → `arch.layering`
284
303
 
285
304
  **LLM-judgment checks**:
305
+
286
306
  - Module/package boundary clarity (read top-level dirs, judge cohesion)
287
307
  - Public vs internal surface intentionality (exports/`__init__.py`/`pub`)
288
308
  - Abstraction quality (over- or under-abstracted code)
@@ -292,6 +312,7 @@ For each dimension, the rubric specifies:
292
312
  **N/A**: repo has <10 source files OR flat single-purpose script
293
313
 
294
314
  **Anchors**: see standard 90+/75–89/60–74/40–59/<40 mapping. For Architecture specifically:
315
+
295
316
  - 90+: zero circular deps, clean layering, intentional public surface
296
317
  - 75–89: minor coupling smells, no cycles, mostly clean
297
318
  - 60–74: 1–2 cycles OR multiple layering violations
@@ -303,6 +324,7 @@ For each dimension, the rubric specifies:
303
324
  **Measures**: clarity, simplicity, craftsmanship of the code itself.
304
325
 
305
326
  **Tool-driven checks**:
327
+
306
328
  - `eslint --format json` (JS/TS) → `cq.lint`
307
329
  - `ruff check --output-format json` (Python) → `cq.lint`
308
330
  - `golangci-lint run --out-format json` (Go) → `cq.lint`
@@ -314,6 +336,7 @@ For each dimension, the rubric specifies:
314
336
  - `vulture` (Python dead code) → `cq.dead_code`
315
337
 
316
338
  **LLM-judgment checks**:
339
+
317
340
  - Naming clarity (sample 20 functions/files, judge expressiveness)
318
341
  - Magic numbers / strings without named constants
319
342
 
@@ -339,6 +362,7 @@ These are measurement calibration, not different standards.
339
362
  **Measures**: tests exist, run, and meaningfully cover behavior.
340
363
 
341
364
  **Tool-driven checks**:
365
+
342
366
  - Read `coverage/coverage-summary.json` (JS/TS via jest/vitest) → `test.coverage_breadth`
343
367
  - Run `vitest run --coverage --reporter=json` if no coverage file exists and project uses vitest → same
344
368
  - `pytest --cov --cov-report=json` (Python, if pytest detected) → `test.coverage_breadth`
@@ -347,6 +371,7 @@ These are measurement calibration, not different standards.
347
371
  - Test:source LOC ratio (via `scc`) → `test.presence`
348
372
 
349
373
  **LLM-judgment checks**:
374
+
350
375
  - Sample test files: are assertions real (`expect(x).toBe(...)`) or vacuous (`expect(x).toBeDefined()`)? → `test.assertion_quality`
351
376
  - Are critical paths (auth, payments, persistence, API surface) tested? Read entry points + grep for corresponding test files → `test.critical_path_coverage`
352
377
  - Test isolation: scan for shared mutable state, `beforeAll` without cleanup → `test.isolation`
@@ -356,6 +381,7 @@ These are measurement calibration, not different standards.
356
381
  **N/A**: doc-only or config-only repo with no executable behavior.
357
382
 
358
383
  **Anchors**: standard mapping. Coverage % thresholds for `coverage_breadth`:
384
+
359
385
  - ≥85%: A-tier (95)
360
386
  - 70–84%: B-tier (80)
361
387
  - 50–69%: C-tier (65)
@@ -367,6 +393,7 @@ These are measurement calibration, not different standards.
367
393
  **Measures**: defensive posture against real-world threats.
368
394
 
369
395
  **Tool-driven checks**:
396
+
370
397
  - `semgrep --config auto --json` (multi-lang SAST) → `sec.sast`
371
398
  - `gitleaks detect --report-format json` → `sec.secrets`
372
399
  - `npm audit --json` / `pnpm audit --json` / `yarn audit --json` → `sec.dep_vulns`
@@ -379,6 +406,7 @@ These are measurement calibration, not different standards.
379
406
  - `osv-scanner --json --recursive .` (multi-lang CVE) → `sec.dep_vulns` (cross-check)
380
407
 
381
408
  **LLM-judgment checks**:
409
+
382
410
  - AuthN/AuthZ logic at entry points (sample handlers)
383
411
  - Unsafe deserialization patterns (Grep: `pickle.load`, `yaml.load`, `eval(`, `Function(`)
384
412
  - Crypto: deprecated algorithms (Grep: `md5`, `sha1` in security contexts, `Math.random` for tokens)
@@ -392,11 +420,13 @@ These are measurement calibration, not different standards.
392
420
  **Measures**: efficiency in hot paths. Real cost, not micro-optimization.
393
421
 
394
422
  **Tool-driven checks**:
423
+
395
424
  - Bundle size (JS): `size-limit --json` if configured, else parse webpack/vite output → `perf.bundle_size`
396
425
  - Database query patterns: Semgrep with N+1 rule pack → `perf.n_plus_one`
397
426
  - ESLint perf rules (`eslint-plugin-react-perf`, etc., if configured) → `perf.framework_specific`
398
427
 
399
428
  **LLM-judgment checks**:
429
+
400
430
  - Synchronous I/O in async contexts (Grep + read)
401
431
  - Unbounded operations on user input (regex backtracking, loops, allocations)
402
432
  - Missing pagination on list endpoints
@@ -416,11 +446,13 @@ These are measurement calibration, not different standards.
416
446
  **Measures**: ability of a new engineer to understand, run, change the project.
417
447
 
418
448
  **Tool-driven checks**:
449
+
419
450
  - README presence + size (`wc -l README*`) → `doc.readme`
420
451
  - API doc coverage: `typedoc --json` (TS), `pydoc-markdown` (Python), `cargo doc` warnings (Rust), `godoc` (Go) → `doc.api_docs`
421
452
  - ADR presence (`ls docs/adr/` or `ls docs/decisions/`) → `doc.decision_records`
422
453
 
423
454
  **LLM-judgment checks**:
455
+
424
456
  - README quality: purpose / install / run / test / develop / contribute sections present and accurate?
425
457
  - Architecture overview present?
426
458
  - Inline comments on non-obvious code (sample 20 functions)
@@ -435,6 +467,7 @@ These are measurement calibration, not different standards.
435
467
  **Measures**: long-term cost of working in this codebase.
436
468
 
437
469
  **Tool-driven checks**:
470
+
438
471
  - `scc --format json` → file-size + LOC distribution → `maint.file_distribution`
439
472
  - Lizard / Radon maintainability index → `maint.cognitive_load`
440
473
  - `depcheck` (JS unused deps) / `vulture` / `cargo udeps` → contributes to `maint.tooling_enforcement` if linter+coverage are configured
@@ -443,10 +476,12 @@ These are measurement calibration, not different standards.
443
476
  - Pre-commit hook presence: `ls .husky/` / `.pre-commit-config.yaml`
444
477
 
445
478
  **LLM-judgment checks**:
479
+
446
480
  - TODO/FIXME density (`git grep -n "TODO\|FIXME" | wc -l`) + age sample (`git blame` on a sample) → `maint.todo_debt`
447
481
  - Configuration sprawl (multiple `.config` files for same concern)
448
482
 
449
483
  **External signals**:
484
+
450
485
  - Git churn hotspots → evidence for `maint.churn_hotspots` subscore
451
486
  - GitHub Issues open count + age → evidence here too
452
487
 
@@ -459,6 +494,7 @@ These are measurement calibration, not different standards.
459
494
  **Measures**: third-party supply chain hygiene.
460
495
 
461
496
  **Tool-driven checks**:
497
+
462
498
  - `npm outdated --json` / `pnpm outdated --format json` / `yarn outdated --json` → `dep.freshness`
463
499
  - `pip list --outdated --format=json` (Python) → `dep.freshness`
464
500
  - `cargo outdated --format json` (Rust) → `dep.freshness`
@@ -469,6 +505,7 @@ These are measurement calibration, not different standards.
469
505
  - Lockfile presence + age (`git log -1 --format=%ar` on lockfile) → `dep.lockfile`
470
506
 
471
507
  **LLM-judgment checks**:
508
+
472
509
  - Upstream maintenance status (for top 10 direct deps: last release date via tool output; archived flag) → `dep.upstream_maintenance`
473
510
 
474
511
  **Subscores**: `lockfile`, `freshness`, `unused`, `license`, `upstream_maintenance`
@@ -484,6 +521,7 @@ These are measurement calibration, not different standards.
484
521
  Tool catalog is the **authoritative list of commands** you may run. You may not invent commands or flags.
485
522
 
486
523
  Each entry:
524
+
487
525
  ```
488
526
  <tool-id>:
489
527
  category: <category>
@@ -782,6 +820,33 @@ bundler-audit:
782
820
  subscores: [security.dep_vulns]
783
821
  ```
784
822
 
823
+ ### C# / .NET
824
+
825
+ Both tools ship with the .NET SDK — no extra install, no Go. `--format json`
826
+ requires SDK >= 9. They trigger a `dotnet restore` first; if the repo has no
827
+ network access to NuGet the restore (and thus the listing) degrades to
828
+ unmeasured.
829
+
830
+ ```
831
+ dotnet-list-vulnerable:
832
+ applies_to: [cs]
833
+ probe: command -v dotnet
834
+ install: null # bundled with the .NET SDK
835
+ command: dotnet restore >/dev/null 2>&1; dotnet list package --vulnerable --include-transitive --format json
836
+ timeout_minutes: 5
837
+ parser: dotnet-list-vulnerable
838
+ subscores: [security.dep_vulns]
839
+
840
+ dotnet-list-outdated:
841
+ applies_to: [cs]
842
+ probe: command -v dotnet
843
+ install: null # bundled with the .NET SDK
844
+ command: dotnet restore >/dev/null 2>&1; dotnet list package --outdated --format json
845
+ timeout_minutes: 5
846
+ parser: dotnet-list-outdated
847
+ subscores: [dependency_health.freshness]
848
+ ```
849
+
785
850
  ### Multi-language
786
851
 
787
852
  ```
@@ -918,6 +983,7 @@ If Sentry MCP is not configured, log under `external_signals.sentry.unavailable:
918
983
  - `cwe` optional, array of CWE IDs
919
984
 
920
985
  Severity guidance:
986
+
921
987
  - `critical`: vulnerable/broken in production
922
988
  - `high`: clear defect that will bite
923
989
  - `medium`: real smell with measurable cost
@@ -933,7 +999,11 @@ Severity guidance:
933
999
  "effort": "medium",
934
1000
  "impact": "high",
935
1001
  "description": "Replace string interpolation in db.query() calls under src/payments/ with parameterized queries. Use the existing pg.query(text, values) signature. 4–6 sites in charge.ts, refund.ts, list.ts.",
936
- "files": ["src/payments/charge.ts", "src/payments/refund.ts", "src/payments/list.ts"],
1002
+ "files": [
1003
+ "src/payments/charge.ts",
1004
+ "src/payments/refund.ts",
1005
+ "src/payments/list.ts"
1006
+ ],
937
1007
  "blocks_evidence": ["semgrep:javascript.lang.security.audit.sqli..."]
938
1008
  }
939
1009
  ```
@@ -990,21 +1060,42 @@ End your response with **exactly one** JSON code block in this shape:
990
1060
  "jscpd": "4.0.5"
991
1061
  },
992
1062
  "unavailable_tools": [
993
- { "name": "gitleaks", "category": "security", "install_command": "go install github.com/zricethezav/gitleaks/v8@latest", "reason": "install_failed: go not present" }
1063
+ {
1064
+ "name": "gitleaks",
1065
+ "category": "security",
1066
+ "install_command": "go install github.com/zricethezav/gitleaks/v8@latest",
1067
+ "reason": "install_failed: go not present"
1068
+ }
994
1069
  ],
995
1070
  "applied_checks": {
996
1071
  "architecture": [
997
- { "id": "arch.circular_deps", "tool": "madge", "weight": 1.0, "measured": true },
998
- { "id": "arch.layering", "tool": "llm_judgment", "weight": 1.0, "measured": true }
1072
+ {
1073
+ "id": "arch.circular_deps",
1074
+ "tool": "madge",
1075
+ "weight": 1.0,
1076
+ "measured": true
1077
+ },
1078
+ {
1079
+ "id": "arch.layering",
1080
+ "tool": "llm_judgment",
1081
+ "weight": 1.0,
1082
+ "measured": true
1083
+ }
999
1084
  ]
1000
1085
  },
1001
1086
  "tool_outputs": {
1002
- "eslint": { "ran_at": "...", "duration_ms": 4200, "exit_code": 0, "findings_count": 23, "summary": "..." }
1087
+ "eslint": {
1088
+ "ran_at": "...",
1089
+ "duration_ms": 4200,
1090
+ "exit_code": 0,
1091
+ "findings_count": 23,
1092
+ "summary": "..."
1093
+ }
1003
1094
  },
1004
1095
  "external_signals": {
1005
1096
  "git": {
1006
1097
  "authors_90d": 4,
1007
- "top_churn_files": [{"file": "src/api/handlers.ts", "commits_30d": 18}],
1098
+ "top_churn_files": [{ "file": "src/api/handlers.ts", "commits_30d": 18 }],
1008
1099
  "bug_fix_commits_90d": 12
1009
1100
  },
1010
1101
  "github_issues": { "open_bugs": 7, "open_security": 0 },
@@ -1018,14 +1109,30 @@ End your response with **exactly one** JSON code block in this shape:
1018
1109
  "subscores": {
1019
1110
  "boundaries": { "value": 85, "measured_coverage": 1.0 },
1020
1111
  "coupling": { "value": 72, "measured_coverage": 1.0 },
1021
- "layering": { "value": 80, "measured_coverage": 0.5, "summary": "Limited measurement — dependency-cruiser not configured." },
1112
+ "layering": {
1113
+ "value": 80,
1114
+ "measured_coverage": 0.5,
1115
+ "summary": "Limited measurement — dependency-cruiser not configured."
1116
+ },
1022
1117
  "abstraction_quality": { "value": 75, "measured_coverage": 1.0 }
1023
1118
  },
1024
1119
  "evidence": [
1025
- { "file": "src/auth/session.ts", "line": 4, "issue": "Imports src/users/profile.ts which transitively imports back", "severity": "medium", "source": "tool:madge" }
1120
+ {
1121
+ "file": "src/auth/session.ts",
1122
+ "line": 4,
1123
+ "issue": "Imports src/users/profile.ts which transitively imports back",
1124
+ "severity": "medium",
1125
+ "source": "tool:madge"
1126
+ }
1026
1127
  ],
1027
1128
  "recommendations": [
1028
- { "title": "Break auth ↔ users circular dependency", "effort": "low", "impact": "medium", "description": "Move shared User type to src/shared/types/user.ts.", "files": ["src/auth/session.ts", "src/users/profile.ts"] }
1129
+ {
1130
+ "title": "Break auth ↔ users circular dependency",
1131
+ "effort": "low",
1132
+ "impact": "medium",
1133
+ "description": "Move shared User type to src/shared/types/user.ts.",
1134
+ "files": ["src/auth/session.ts", "src/users/profile.ts"]
1135
+ }
1029
1136
  ],
1030
1137
  "n_a_reason": null
1031
1138
  }
@@ -1039,6 +1146,7 @@ End your response with **exactly one** JSON code block in this shape:
1039
1146
  ```
1040
1147
 
1041
1148
  For N/A dimensions:
1149
+
1042
1150
  ```json
1043
1151
  "performance": {
1044
1152
  "score": null,
@@ -434,6 +434,42 @@ const bundlerAudit = {
434
434
  tolerate_nonzero_exit: true,
435
435
  };
436
436
  // ---------------------------------------------------------------------------
437
+ // C# / .NET
438
+ //
439
+ // Both tools below ship with the .NET SDK itself — no extra install, no Go,
440
+ // no network beyond the NuGet restore they trigger. `--format json` requires
441
+ // SDK >= 9; on older SDKs the command exits non-zero and degrades to
442
+ // unmeasured (tolerate_nonzero_exit + a defensive parser).
443
+ // ---------------------------------------------------------------------------
444
+ const dotnetListVulnerable = {
445
+ id: 'dotnet-list-vulnerable',
446
+ label: 'dotnet list package --vulnerable',
447
+ category: 'dep-vuln',
448
+ applies_to: ['cs'],
449
+ probe: 'command -v dotnet',
450
+ install: null, // bundled with the .NET SDK
451
+ install_prereq: null,
452
+ command: 'dotnet restore >/dev/null 2>&1; dotnet list package --vulnerable --include-transitive --format json',
453
+ timeout_minutes: 5,
454
+ parser: 'dotnet-list-vulnerable',
455
+ subscores: ['security.dep_vulns'],
456
+ tolerate_nonzero_exit: true,
457
+ };
458
+ const dotnetListOutdated = {
459
+ id: 'dotnet-list-outdated',
460
+ label: 'dotnet list package --outdated',
461
+ category: 'dep-outdated',
462
+ applies_to: ['cs'],
463
+ probe: 'command -v dotnet',
464
+ install: null, // bundled with the .NET SDK
465
+ install_prereq: null,
466
+ command: 'dotnet restore >/dev/null 2>&1; dotnet list package --outdated --format json',
467
+ timeout_minutes: 5,
468
+ parser: 'dotnet-list-outdated',
469
+ subscores: ['dependency_health.freshness'],
470
+ tolerate_nonzero_exit: true,
471
+ };
472
+ // ---------------------------------------------------------------------------
437
473
  // Multi-language / polyglot
438
474
  // ---------------------------------------------------------------------------
439
475
  const semgrep = {
@@ -541,6 +577,9 @@ export const TOOL_CATALOG = [
541
577
  rubocop,
542
578
  brakeman,
543
579
  bundlerAudit,
580
+ // C# / .NET
581
+ dotnetListVulnerable,
582
+ dotnetListOutdated,
544
583
  // Polyglot
545
584
  semgrep,
546
585
  gitleaks,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * sync-github-pull-requests phase: pull every pull request from a repository's
3
+ * connected GitHub repo and mirror them into the `pull_requests` table scoped
4
+ * by `repository_id`. Deterministic — no Claude Agent SDK; plain Octokit plus
5
+ * the user's RLS-scoped Supabase session.
6
+ *
7
+ * Idempotent: rows are keyed on (repository_id, pull_request_number). New PRs
8
+ * are inserted, and the GitHub-owned fields (status/title/description) of
9
+ * already-synced PRs are refreshed. Re-running never duplicates a PR.
10
+ */
11
+ import { Octokit } from '@octokit/rest';
12
+ import { type GitHubPullRequestLite, type SyncPullRequestsResult } from './types.js';
13
+ export interface SyncGithubPullRequestsOptions {
14
+ repositoryId: string;
15
+ /** GitHub installation token (or PAT). */
16
+ githubToken: string;
17
+ owner: string;
18
+ repo: string;
19
+ verbose?: boolean;
20
+ }
21
+ /** Pull every pull request from the repo, paginated (state=all). */
22
+ export declare function fetchAllRepoPullRequests(octokit: Octokit, owner: string, repo: string): Promise<GitHubPullRequestLite[]>;
23
+ export declare function syncGithubPullRequests(options: SyncGithubPullRequestsOptions): Promise<SyncPullRequestsResult>;