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.
- package/dist/api/adr.d.ts +48 -0
- package/dist/api/adr.js +139 -0
- package/dist/commands/adr/index.d.ts +13 -0
- package/dist/commands/adr/index.js +31 -0
- package/dist/commands/features/index.d.ts +15 -0
- package/dist/commands/features/index.js +34 -0
- package/dist/commands/pr-resolve/index.d.ts +3 -1
- package/dist/commands/pr-resolve/index.js +12 -7
- package/dist/commands/pr-review/index.d.ts +3 -1
- package/dist/commands/pr-review/index.js +10 -6
- package/dist/commands/sync-github-pull-requests/index.d.ts +11 -0
- package/dist/commands/sync-github-pull-requests/index.js +42 -0
- package/dist/index.js +66 -4
- package/dist/phases/adr-generation/agent.d.ts +6 -0
- package/dist/phases/adr-generation/agent.js +69 -0
- package/dist/phases/adr-generation/index.d.ts +15 -0
- package/dist/phases/adr-generation/index.js +66 -0
- package/dist/phases/adr-generation/parse.d.ts +12 -0
- package/dist/phases/adr-generation/parse.js +123 -0
- package/dist/phases/adr-generation/prompts.d.ts +8 -0
- package/dist/phases/adr-generation/prompts.js +35 -0
- package/dist/phases/data-flow/mcp-server.d.ts +1 -1
- package/dist/phases/features/index.d.ts +65 -0
- package/dist/phases/features/index.js +292 -0
- package/dist/phases/features/mcp-server.d.ts +61 -0
- package/dist/phases/features/mcp-server.js +165 -0
- package/dist/phases/features/prompts.d.ts +32 -0
- package/dist/phases/features/prompts.js +92 -0
- package/dist/phases/features/types.d.ts +34 -0
- package/dist/phases/features/types.js +15 -0
- package/dist/phases/pr-resolve/index.d.ts +3 -1
- package/dist/phases/pr-resolve/index.js +12 -12
- package/dist/phases/pr-review/index.d.ts +3 -1
- package/dist/phases/pr-review/index.js +13 -16
- package/dist/phases/pr-shared/status.d.ts +18 -0
- package/dist/phases/pr-shared/status.js +37 -0
- package/dist/phases/quality-benchmark/parsers.js +79 -0
- package/dist/phases/quality-benchmark/rubric.md +125 -17
- package/dist/phases/quality-benchmark/tool-catalog.js +39 -0
- package/dist/phases/sync-github-pull-requests/index.d.ts +23 -0
- package/dist/phases/sync-github-pull-requests/index.js +210 -0
- package/dist/phases/sync-github-pull-requests/state.d.ts +24 -0
- package/dist/phases/sync-github-pull-requests/state.js +16 -0
- package/dist/phases/sync-github-pull-requests/types.d.ts +22 -0
- package/dist/phases/sync-github-pull-requests/types.js +1 -0
- package/dist/skills/phase/adr-generation/SKILL.md +51 -0
- 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
|
|
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
|
|
186
|
-
|
|
198
|
+
| Score | Grade | Meaning |
|
|
199
|
+
| ------ | ----- | ----------------------------------------------------------------------- |
|
|
187
200
|
| 90–100 | **A** | Exemplary. Top-tier engineering org review would surface only nitpicks. |
|
|
188
|
-
| 75–89
|
|
189
|
-
| 60–74
|
|
190
|
-
| 40–59
|
|
191
|
-
| 0–39
|
|
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
|
|
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": [
|
|
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
|
-
{
|
|
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
|
-
{
|
|
998
|
-
|
|
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": {
|
|
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": {
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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>;
|