Evaluationruleset 2026.10.4dataset b65381532aba…generated 2026-10-08
What the evaluation shows, and what it cannot.
67 labelled code changes, two naive baselines, an ablation without the repository snapshot, and three local language models, all scored with one-to-one matching and bootstrap intervals over cases. The dataset is small and synthetic, and the same author wrote it and the rules. Read every number with that in mind.
- Cases
- 67
- 33 dev · 21 holdout · 13 holdout v2
- Labels
- 65
- where a reviewer should look
- Negative controls
- 9
- safe changes; any finding is a false alarm
- Bootstrap
- 2,000
- resamples over cases, seed 20261008
1Clean holdout results
Start with the numbers nothing was tuned on.
The rules were written against the dev split. Two holdout sets were written afterwards, with the ruleset frozen, and each was run exactly once before anything changed. Those first runs are the closest thing here to an unbiased estimate. Every fix they prompted is listed, and later numbers on the same split no longer count as clean.
Precision
- ChangeGuard83.3%
- ChangeGuard, diff only90.9%
- Keyword search baseline57.1%
- Flag-every-change baseline27.3%
Recall
- ChangeGuard75.0%
- ChangeGuard, diff only50.0%
- Keyword search baseline40.0%
- Flag-every-change baseline15.0%
False alarms on safe changes (ChangeGuard): 67% of 3 negative controls.
eval/reports/runs/holdout-first-run-ruleset-2026.10.1.txt.Precision
- ChangeGuard100.0%
- ChangeGuard, diff only100.0%
- Keyword search baseline42.9%
- Flag-every-change baseline42.9%
Recall
- ChangeGuard81.8%
- ChangeGuard, diff only63.6%
- Keyword search baseline27.3%
- Flag-every-change baseline27.3%
False alarms on safe changes (ChangeGuard): 0% of 2 negative controls.
eval/reports/runs/holdout-v2-first-run-ruleset-2026.10.2.txt.Changed after the holdout first run
- Fixed: JavaScript/TypeScript methods were treated as having an implicit
selfparameter, hiding new required parameters (ho-ts-method-001). - Fixed: adding a return annotation where none existed was reported as a return-type change (ho-py-neg-logging-001).
- Changed: pure renames and private helpers whose same-file callers are tested no longer produce test-gap findings (ho-ts-neg-rename-001).
- Added: yaml.unsafe_load / marshal.loads / jsonpickle.decode detection, not covered by Ruff (ho-py-unsafe-load-001).
- Label correction: an omitted acceptable test-gap entry was added to ho-py-dataclass-001, per LABELING.md section 4.
Changed after the holdout v2 first run
- Added: verify=False passed through Session/Client methods (Ruff S501 only matches module-level requests calls) (h2-py-session-verify-001).
2All cases, current ruleset
All 67 cases with the current rules, intervals included.
These numbers include the dev split the rules were written against and the holdout cases after their fixes, so they are optimistic by construction. The intervals are 95% percentile bootstrap intervals over cases; with this few cases they are wide, and a perfect point estimate still comes with a lower bound.
The ablation is the most informative row. Without the repository snapshot, the same rules keep their precision but recall falls from 93.8% to 72.3%: the missing share is what cross-file analysis (callers, imports, tests) contributes.
Precision
95% CI- ChangeGuard100.0%
- ChangeGuard, diff only100.0%
- Keyword search baseline59.3%
- Flag-every-change baseline31.4%
Recall
95% CI- ChangeGuard93.8%
- ChangeGuard, diff only72.3%
- Keyword search baseline49.2%
- Flag-every-change baseline16.9%
Precision
95% CI- Dev100.0%34 TP · 0 FP · 0 FN
- Holdout100.0%17 TP · 0 FP · 3 FN
- Holdout v2100.0%10 TP · 0 FP · 1 FN
Recall
95% CI- Dev100.0%34 TP · 0 FP · 0 FN
- Holdout85.0%17 TP · 0 FP · 3 FN
- Holdout v290.9%10 TP · 0 FP · 1 FN
3What it misses
4 labels missed, and why no rule caught them.
Each miss below is a change whose problem lies in its meaning rather than its shape. Nothing syntactic distinguishes these lines from a correct edit, so a rule that flagged them would also flag a great many correct edits.
This is where a language model could earn its place, and it partly does: qwen2.5-coder:7b found 2 of these 4. The section on language models shows what that cost.
h2-py-naive-datetime-001Holdout v2L1
UTC timestamps become naive local time
Audit timestamps lose their timezone; ordering across hosts in different zones breaks. No syntactic rule targets this.
Behaviour changesrc/audit/stamp.py:5
Not found by any evaluated system.
ho-py-semantic-001HoldoutL1
Operands swapped in a refund calculation
The subtraction is reversed, so refunds become negative. No syntactic rule targets this.
Logic changesrc/billing/refunds.py:2
Found by qwen2.5-coder:7b; qwen2.5-coder:7b (3-sample vote)
ho-py-semantic-002HoldoutL1
Quantity dropped from a total during a refactor
While adding a discount, the per-line quantity multiplication disappears; the only test uses quantity 1 and still passes.
Logic changesrc/cart/totals.py:13
Found by qwen2.5-coder:7b; qwen2.5-coder:7b (3-sample vote); Keyword search baseline
ho-py-test-deleted-001HoldoutL2
Password tests deleted while the KDF cost is halved
The only tests for password verification are deleted in the same change that halves the PBKDF2 iteration count.
Securitysrc/auth/password.py:6
Not found by any evaluated system.
4Language models
Models ground their claims. Most of their new risks are still wrong.
Each model ran locally through ollama on all 67 cases with the same prompt (v1.0.0), greedy decoding, and a fixed seed. Every response is recorded, so these numbers replay exactly in CI without a model. The vote row samples three answers at temperature 0.7 and keeps a proposed risk only if a majority of samples agree.
Two results hold across models. First, grounding works: no system cited evidence that failed the independent re-check, and the verifier rejected the claims that named findings, paths, or symbols the evidence did not support. Second, grounded is not the same as right. The larger model proposed many additional risks that cite real evidence and still match no label. That is why model findings sit in their own lane, are capped at medium confidence, and can never fail a build.
Precision
95% CI- ChangeGuard100.0%
- + llama3.2:3b98.4%
- + qwen2.5-coder:7b53.4%
- + qwen2.5-coder:7b, 3-sample vote65.0%
Recall
95% CI- ChangeGuard93.8%
- + llama3.2:3b93.8%
- + qwen2.5-coder:7b96.9%
- + qwen2.5-coder:7b, 3-sample vote96.9%
llama3.2:3b
Why claims were rejected
unknown_finding14symbol_not_in_evidence5no_evidence1unknown_path1
qwen2.5-coder:7b
Why claims were rejected
unknown_path2symbol_not_in_evidence1
qwen2.5-coder:7b@3
Why claims were rejected
low_agreement23unknown_path2symbol_not_in_evidence1
Self-consistency helps, at a price. Voting over three samples dropped 21 of 57 model-proposed risks while keeping every one that matched a label, at roughly three times the tokens. The smaller model is quieter: it rarely proposes risks and mostly attaches explanations to findings the rules already made.
Not measured: whether the explanations are correct or useful. That needs human rating, and none has been done.
5Provenance and severity
Precision by how a finding was established.
Deterministic and heuristic findings are counted separately, as are confidence levels. A finding is acceptable when the case lists it as a reasonable extra observation, or when it is a second finding on a label already matched; acceptable findings count as neither true nor false positives. Most heuristic findings land there: test gaps a reviewer would agree with, but that the labels do not require.
Severity agreement on matched findings
- Exact
- 93.4%
- Within one level
- 100.0%
Severity is a categorical judgement per rule, compared with the labeller’s. It is not a calibrated probability and is never presented as one.
6Safe changes and evidence
Silence on safe changes, and evidence that checks out.
9 cases are safe changes with no label at all, so any finding on them is a false alarm. A review tool that cries wolf gets ignored, which makes this the number reviewers feel most.
Separately, an independent checker re-reads each case’s own files and verifies every evidence item a finding cites: it must exist, point at lines that exist in the stated revision, and quote those lines verbatim. A finding with any evidence that fails counts as unsupported.
- ChangeGuard0/9
- + llama3.2:3b0/9
- + qwen2.5-coder:7b9/9
- + qwen2.5-coder:7b, 3-sample vote7/9
- ChangeGuard, diff only0/9
- Keyword search baseline4/9
- Flag-every-change baseline9/9
7Method
How the numbers are produced.
Dataset
67 cases, each a patch with the repository before the change and, for some, a coverage report. Each case lists expected labels (category, file, lines, severity) and acceptable extra observations. Labelling rules are in eval/LABELING.md.
Matching
Same category and file, line ranges overlapping within ±3 lines, one-to-one. Precision and recall are micro-averaged over all labels.
Intervals
95% percentile bootstrap over cases, 2,000 resamples, seed 20261008, overall and per split.
Baselines
A keyword search over added lines, and a system that flags every changed function. The diff-only ablation runs the same rules without the repository snapshot.
Language models
Local inference through ollama, temperature 0 and seed 7. Responses are recorded per request fingerprint and replayed exactly.
Regression gate
CI replays the evaluation against a committed baseline. It fails if precision, recall or F1 drops by more than 2 points, the false-alarm rate rises by more than 5, unsupported evidence increases, or a previously found label is missed.
cd backendMODELS="llama3.2:3b,qwen2.5-coder:7b,qwen2.5-coder:7b@3"uv run changeguard eval --ai replay \ --ai-model "$MODELS" \ --check-baseline ../eval/baselines/changeguard.json8Limitations
What these numbers do not say.
- 01The dataset is small and synthetic: each case was written for this project to exercise a specific risk pattern. Results measure agreement with the labelling guideline on these cases, not effectiveness on real repositories.
- 02The rules and the dev-split cases were written by the same author, so dev-split numbers are optimistic by construction. The holdout splits were written after the ruleset was frozen; their first runs, recorded before any holdout-informed change, are the more honest estimate, but they share the same author and style.
- 03Bootstrap intervals resample cases; with this few cases they are wide and should be read as rough bounds.
- 04Labels mark where a reviewer should look, not whether the change causes a production incident. Nothing here estimates incident probability.
- 05AI metrics measure grounding (cited evidence exists and matches) and agreement with labels. They do not measure the correctness or usefulness of explanations, which would require human rating.
Per-case results for ChangeGuard (67 cases)
Want to see what a single analysis looks like? Walk through one change or read how each rule works.