passes unexpected keyword argument currency
does not pass required argument locale
Interactive walkthrough9 chaptersengine 0.1.0 · ruleset 2026.10.4
A developer adds loyalty discounts to a billing service and switches price formatting from currency codes to locales. The diff is 29 lines across 3 files and reads like routine work. This page runs it through ChangeGuard one stage at a time and shows what each stage established, and how.
The sample is synthetic. A small repository written to exercise the analysers, labelled as such everywhere it appears.
The output is real. Every number and finding below was produced by the engine. Run it yourself and the finding IDs should match.
Start the engine to run the sample live. Until then the figure replays a run recorded by the engine at build time.
Event log
Ready.
01Parse inputs0.68 msReconstruct revisions0.08 ms
ChangeGuard accepts up to three inputs: the patch, a snapshot of the repository before the change, and a coverage report. All three are present here: a git patch touching 3 files (+24 −5), a 6-file snapshot, and a Cobertura report from the test suite.
The patch is parsed strictly. A stray line after a hunk is an error, not something to skip. The parsed patch is then applied to the snapshot in memory to rebuild both revisions. Nothing is written to disk, no archive is extracted, and no script, hook, or test from the repository runs. Every later stage reads these two in-memory revisions.
Before going further, read the diff yourself. Which lines would you stop at? When you have an answer, reveal what the engine flagged; the markers sit on the exact lines its findings point at.
02Structural diff1.59 ms
A line diff knows which lines moved. It does not know that format_price is a function, that it had a defaulted currency parameter, or that the new version requires a locale. ChangeGuard parses both revisions with tree-sitter and compares them symbol by symbol: parameters, with their order, kinds and defaults, and return annotations.
Two signatures changed. apply_discount gained a parameter with a default, so every existing call still binds. format_price lost currency and gained a required locale, and the structural diff marks it breaking. Whether that matters depends on who calls it, which is the next stage.
async handle_invoice(request_body: str) -> dict[str, str]loyalty_bonus(years: int, tier: str) -> Decimaltotal_with_tax(subtotal: Decimal, region: str) -> Decimal03Cross-file references1.01 ms
For each changed signature, ChangeGuard resolves imports across the snapshot to find the call sites. Here those are from billing.pricing import … in invoice.py and in the tests. It then replays each call against the new parameter list using Python’s binding rules: positional slots, keywords, defaults, *args and **kwargs.
6 call sites were checked and 2 no longer bind. render_line passes currency=, a keyword the function no longer accepts, and a test calls format_price with no locale at all. Both would raise TypeError when they run. Together they make one deterministic, high-severity finding, with the call sites as its evidence.
passes unexpected keyword argument currency
does not pass required argument locale
Incompatible call sites are kept as evidence with their exact location. Compatible ones are counted but not stored.
04Static analysis24 ms
A linter run over a whole repository buries a reviewer in findings that predate the change. ChangeGuard runs Ruff on both revisions of each changed Python file and keeps only the diagnostics that are new in the head revision. Ruff reads the source through stdin with repository configuration ignored, so nothing in the repository can change how the tool behaves, and the analysed code is never executed.
5 diagnostics are new. Two of them, E722 and S110, describe the same bare except: pass on line 28. The report merges them into one finding and records the second rule as corroboration instead of counting the problem twice.
Read the two pricing findings together. REGIONAL_RATES on line 27 is never defined, so the lookup raises NameError, and the bare except on the next line swallows it. Nothing crashes. Every non-EU invoice silently loses its regional tax.
Async functions should not call time.sleep
print found
Undefined name REGIONAL_RATES
Do not use bare except
05Risk rules1.49 ms
Some risks have no linter: credentials pasted into code, text written to steer a language model, a migration that drops a column, a test whose assertions were deleted. ChangeGuard’s rule set reads the added lines for these. On this change every family came back clear except one.
percent > 50 became percent >= 50. The docstring above it still says discounts above 50% need approval; now exactly 50% does too. Whether that is a fix or a regression is a business question, so the rule makes a heuristic finding with medium confidence, and the suggested check is a boundary-value test.
The one match · src/billing/pricing.py:11 · E10
Highlighted: the unchanged docstring, line 9, which still says above.
Heuristic · CG-LOG-001
Condition logic changed
Suggested check. Add boundary-value tests: the threshold itself, one below, and one above, asserting the intended branch for each.
06Test mapping0.38 msCoverage0.42 ms
There are two independent signals here. Test mapping is static: it looks for test files that import a changed symbol’s module and reference the symbol. 2 of the 5 changed symbols have such tests. The others, including the new loyalty_bonus and the async handler, have none.
Coverage is measured. The uploaded report records hit counts per line, and ChangeGuard intersects them with the changed lines of the head revision: 4 of 19 changed executable lines ran, which is 21.1% patch coverage. The report gets a plausibility check first. Hits on blank lines, comments, or lines past the end of a file mark it as stale, and that lowers the confidence of every coverage finding instead of being trusted at face value.
Static: found by reading test files, not by running them.
src/billing/api.py0/3
src/billing/loyalty.py0/8
src/billing/pricing.py4/8
07AI synthesis24.1 sGrounding verification0.45 ms
With AI enabled, ChangeGuard sends a model a fenced evidence pack containing the findings, their evidence, and the changed code, with each excerpt wrapped as untrusted data. Secrets are masked before the pack is built, and any line that reads like an instruction to the model is withheld. The model must answer in a closed JSON schema in which every claim cites evidence IDs, and the schema has no file or line fields to fill in.
The output then goes to a verifier. It rejects a claim outright, and never repairs it, if the claim cites evidence that does not exist, names a path outside the change, gives a line number outside the cited evidence, attributes a function the evidence does not contain, uses a number the evidence does not support, or reports a test result. ChangeGuard runs no tests, so no model may say one passed.
In the recorded run below, llama3.2:3b, running locally, made 2 claims and 2 passed. One became a note on the call-site finding. It also wrote a free-text summary that puts the condition change in format_price. The change is in apply_discount. That summary is kept in the trace and never shown as analysis.
01
Evidence pack
9,011 chars · trimmed to budget
excerpts fenced as untrusted
secrets masked · injection lines withheld
02
Model
llama3.2:3b
ollama · local inference
3,510 → 257 tokens · 24.1 s
03
Closed schema
every claim cites evidence IDs
no file or line fields to fill
AI risks: confidence ≤ medium
04
Verifier
8 checks per claim
a failed check rejects the claim
claims are never repaired
05
Result
2 claims · 2 accepted · 0 rejected
shown beside the rule’s analysis, never in place of it
✦ Shownverified note on CG-API-001
2 call site(s) incompatible with new signature of format_price
“The new signature offormat_pricechanges the required parameter fromcurrencytolocale, making it incompatible with existing call sites.”
cites E4 model confidence high
Not shownfree-text summary, kept in the JSON trace
“The riskiest aspect of this change is the potential for incorrect calculations due to the changed condition logic in `format_price`. This could lead to inaccurate invoices being generated, resulting in financial losses. The new signature of `format_price` also introduces a new source of errors if not handled properly.”
It places the condition change in format_price. The report places it in apply_discount. Prose like this cannot be checked claim by claim, so it is never displayed as analysis.
unknown_findingA note must attach to a finding in this report.no_evidenceEvery claim cites at least one evidence ID.unknown_evidenceEvery cited ID was produced for this change.unknown_pathEvery file path named is part of the change or its evidence.line_out_of_rangeEvery line number falls inside the evidence it cites.symbol_not_in_evidenceEvery function named appears in the cited evidence.unsupported_numberEvery percentage appears in the evidence.claims_test_resultNo claim says a test passed or failed. None were run.08Assemble report0.45 ms
Findings are de-duplicated, every evidence reference is checked to exist, and each finding gets a stable ID derived from its rule and location, so the same change produces the same IDs on every run. Severity and confidence are separate categorical judgements, defined per rule. They are not probabilities.
The review priority is a written rule, not a score, and the figure lists it in full. Here 2 high-severity deterministic findings set it to high. A model’s findings could never have pushed it past elevated.
| Deterministic | Heuristic | AI-generated | |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | |
| Medium | 0 | ||
| Low | 0 | 0 |
sets the review priority
First rule that matches, top to bottom. A triage label, not a probability.
Block
A critical finding established by deterministic analysis.
Highthis change
Any high-severity deterministic or heuristic finding.
Elevated
Any medium-severity finding. AI findings alone can raise priority this far and no further.
Routine
Only low-severity or informational findings.
2 call site(s) incompatible with new signature of format_price
src/billing/pricing.py:16 · CG-API-001 · Breaking change · confidence high
09CLI and exports
The CLI runs the identical pipeline on a git range. It writes Markdown for a pull-request comment, SARIF for code scanning, or the full JSON report, and it can fail the build at a severity threshold. AI findings never fail a build. Only deterministic and heuristic findings count.
changeguard analyze \ --git-repo . --base origin/main --head HEAD \ --coverage coverage.xml \ --format sarif --output changeguard.sarif \ --fail-on highThe recorded sample report in each format, as the engine wrote it:
Paste a diff, or upload a patch with a snapshot and a coverage report. Everything above runs the same way, and nothing leaves the machine the engine runs on unless you configure a hosted model.