Catch-rate benchmark
Starter harness for measuring whether CodeVetter catches known bugs in agent-generated changes.
This is the starter harness for measuring whether CodeVetter catches known bugs in agent-generated changes.
Run:
npm run bench:catch-rate
npm run bench:catch-rate -- benchmarks/agent-prs/sample.json --reviewer=codevetter
npm run bench:catch-rate -- --reviewer=codevetter --baseline=baseline
npm run bench:catch-rate -- --reviewer=codevetter --baseline=coderabbit_free
npm run bench:catch-rate -- --reviewer=codevetter --baseline=claude_code_review
npm run bench:catch-rate -- --reviewer=codevetter --min-rate=0.8
npm run bench:catch-rate -- --reviewer=codevetter --max-false-positives=1
npm run bench:catch-rate -- --reviewer=codevetter --max-redundant-matches=0
npm run bench:catch-rate -- --reviewer=codevetter --min-severity-rate=high:1
npm run bench:catch-rate -- --reviewer=codevetter --require-rationales
npm run bench:catch-rate -- --json
npm run bench:catch-rate -- --format=markdown --out=artifacts/benchmark.md
npm run bench:new-case -- --id=owner-repo-pr-123 --title="Agent regresses checkout state" --repo=owner/repo --pr-url=https://github.com/owner/repo/pull/123
npm run bench:curation -- benchmarks/agent-prs/cases
# after adding at least one benchmarks/agent-prs/cases/*.json file
npm run bench:catch-rate -- benchmarks/agent-prs/cases --reviewer=codevetter
npm run test:benchmark
Fixture contract:
- Fixtures can be one combined JSON file with
cases[], one per-case JSON file, or a directory of per-case JSON files. ground_truth: hand-labeled issues that should be caught.reviews.<reviewer>: findings emitted by CodeVetter or a comparator.matched_ground_truth: issue ids the finding correctly catches.- Findings with empty
matched_ground_truthcount as false positives. - Strict mode is on by default and requires each case to include
source.repo; pass--no-strictonly for scratch fixtures. - Publishable-fixture mode:
--require-rationalesrequires non-placeholderground_truth[].evidenceandreviews.<reviewer>[].match_rationaleon every matched finding.
Curation workflow:
- Create one starter file per public PR with
npm run bench:new-case -- --id=<case-id> --title=<title> --repo=<owner/repo> --pr-url=<url>. - Preserve source evidence in
source: public PR URL, diff range, agent/tool name, raw diff artifact, CodeVetter output, CodeRabbit free-tier output, and Claude Code/reviewoutput. - Replace every
TODOwith hand-labeled evidence before publishable scoring. - Run
npm run bench:curation -- benchmarks/agent-prs/casesuntil every case is ready. - Run
npm run bench:catch-rate -- benchmarks/agent-prs/cases --reviewer=codevetter --require-rationales.
Curation readiness checks:
- public source metadata: repo, PR URL, diff range, and agent/tool name,
- preserved raw diff artifact,
- preserved CodeVetter, CodeRabbit free-tier, and Claude Code
/reviewoutput artifacts, - review arrays for
codevetter,coderabbit_free, andclaude_code_review(empty arrays are allowed when a tool produced no findings), - hand-labeled ground truth with non-placeholder evidence,
- matched review findings with non-placeholder rationales.
Metrics:
- Overall catch rate: matched ground-truth issues divided by expected issues.
- By-severity catch rate: the same calculation grouped by
severity. - False positives: reviewer findings that do not match any ground-truth issue.
- Redundant matches: repeated matches to a ground-truth issue already caught by another finding in the same case.
- Precision: caught ground-truth issues divided by caught issues plus false positives plus redundant matches.
- F1: harmonic mean of catch rate and precision.
- Baseline comparison:
--baseline=<reviewer>reports caught/rate/false-positive deltas. - Gate mode:
--min-rate=<0..1>exits non-zero when a selected reviewer falls below the threshold. - False-positive gate mode:
--max-false-positives=<n>exits non-zero when a selected reviewer reports more unmatched findings than allowed. - Redundant-match gate mode:
--max-redundant-matches=<n>exits non-zero when a selected reviewer repeats already-caught ground-truth matches too often. - Severity gate mode:
--min-severity-rate=<severity>:<0..1>exits non-zero when a selected reviewer falls below the threshold for that severity. Repeat the flag for multiple severities. - Report output:
--format=text|json|markdowncontrols stdout format.--jsonis equivalent to--format=json. - Durable artifact output:
--out=<path>writes JSON or Markdown reports to disk. Text mode writes a Markdown report to--outwhile keeping the human text summary on stdout. - Rationale gate:
--require-rationalesexits non-zero if hand-labeled bugs or matched findings lack evidence/rationale text.
The included sample is not a publishable benchmark. It only proves the harness shape. Before making external claims, curate 20-30 real public agent PRs with known bugs, preserve raw diffs and review outputs, and record why each finding matches or misses ground truth.
npm run test:benchmark exercises the CLI gates against the sample fixture and generated temporary fixtures: rationale enforcement, TODO-placeholder rejection, directory/per-case fixture loading, curation readiness reporting, false-positive limits, duplicate match accounting, JSON metrics, and Markdown artifact output.
Results — public 27-case set (2026-07-11)
| Reviewer | Catch rate | Precision | F1 |
|---|---|---|---|
| codevetter (pipeline + similarity dedup) | 1.000 (29/29) | 0.433 | 0.604 |
| codevetter (pipeline, pre-dedup-fix) | 1.000 (29/29) | 0.299 | 0.460 |
| raw-claude (single prompt baseline) | 0.931 (27/29) | 0.397 | 0.557 |
The dedup fix (same-file near-line token-similarity clustering in
dedupe_findings, calibrated on real duplicate/non-duplicate pairs from the
first run) cut raw findings 95→65 and flipped the head-to-head: CodeVetter now
beats the single-prompt baseline on catch rate, precision, AND F1. Remaining
noise is mostly process/verification findings (intentional for agent-PR
review, penalized by defect-only ground truth) plus a few boundary duplicate
pairs below the similarity threshold — deliberately not tuned further to
avoid overfitting 27 cases.
Protocol: every case ran through the REAL production pipeline (risk tiers,
specialists, coordinator, dedup) via the ignored Rust harness
diag_benchmark_generate_codevetter_reviews; raw outputs live in
benchmark/reviews-raw/. Ground-truth mapping was proposed mechanically
(scripts/map-benchmark-reviews.mjs) and then hand-judged per finding under
one rule: a finding matches only if its CORE CLAIM identifies the defect;
process findings (no-tests, shipped-with-comment) never match.
Reading: CodeVetter catches everything raw Claude catches plus the two it missed — but emits 95 findings for 29 defects. The precision loss decomposes as 41 redundant restatements of already-caught defects (the coordinator dedup does not collapse same-defect findings on small diffs — actionable) and 25 unmatched findings, ~20 of which are process/verification findings that are intentional for agent-PR review but score as false positives against defect-only ground truth. With redundants collapsed, precision would be 0.537 (F1 ≈ 0.70); excluding process findings too, 0.85.
These are synthetic single-file cases — the planned real agent-PR case curation still stands before external head-to-head claims.