Compare two graph.json snapshots — a before and an after — and get what moved
on the public API surface: symbols added / removed, and for symbols present in
both, the signature-level changes, each classified breaking, warning, or
info. "What broke between these two commits", at the API level. It complements
codemap check: check guards internal structure, diff
guards outward compatibility.
Signatures are parsed with the stdlib ast (each stored signature is read as
def <sig>: …), so parameter analysis is exact, not string-diffing. Only public
symbols participate — private churn is not an API change.
Scope: one provenance root — the package (core). On a repo-scoped graph (built with
--consumer ./tests and friends) a public function in tests/ is not this package's API,
and counting it as one turns --exit-code into a gate on test churn. R1-C52, reported by
the dogfood target from their own release-gate run: 40 of 47 "added public symbols"
were test functions. The report now names its scope under the verdict, and lists what it
did not judge:
✅ No breaking changes. 7 added, 0 removed, 0 changed.
_Compared: public symbols of root `core` — the package. **Not judged:** 40 public
symbol(s) in `tests`, 3 in `examples` — a consumer root is not this package's API._
A single-package graph has one root by construction, so nothing there changes but the
scope line. diff_api(old, new, root=None) compares every root when that is what you want.
| Change | Severity | Why |
|---|---|---|
| public symbol removed | breaking | callers referencing it break |
| public → private | breaking | removed from the public API |
| kind changed (function ↔ class) | breaking | usage form changes |
| parameter removed | breaking | callers passing it break |
| required parameter added | breaking | existing calls omit it |
| parameter made required (default dropped) | breaking | calls relying on the default break |
parameter made keyword-only (* inserted before it) |
breaking | every positional call breaks |
*args / **kwargs removed |
breaking | calls using them break |
| parameter type changed | warning | may narrow — needs a human's eye |
| return type changed | warning | may narrow — needs a human's eye |
newly @deprecated |
warning | signals coming removal |
| parameter no longer keyword-only | info | widening — old calls still work |
| optional parameter added | info | backward-compatible |
| symbol added | info | new API |
A signature ast cannot parse degrades to a conservative signature-changed
warning — never a false "breaking".
Rebuild both snapshots with the same codemap. Parameter kind only entered the
stored signature in R1-C34: before it, *args was rendered as a parameter named args
with a default of (), **kw as kw={}, and / and the bare * were dropped
entirely. Diffing a pre-R1-C34 snapshot against a newer one therefore reports signature
churn that is the renderer changing, not the code. The two rows about keyword-only above
cannot fire on an old snapshot at all — nothing in it parsed as keyword-only.
# render the delta between two snapshots
codemap diff old.json new.json
# release gate: exit 1 if any breaking change is found
codemap diff old.json new.json --exit-codeBuild the two snapshots from two revisions and diff them:
git stash # or check out the baseline in a worktree
codemap build ./yourpkg -o /tmp/old.json
git stash pop
codemap build ./yourpkg -o /tmp/new.json
codemap diff /tmp/old.json /tmp/new.json --exit-codeExample output:
# API diff — `pkg` → `pkg`
❌ **2 breaking change(s).** 1 added, 1 removed, 1 changed.
## Removed public symbols — breaking (1)
- `pkg.api.Old`
## Breaking signature changes (1)
- `pkg.api.run` — parameter `verbose` is now required
## Added public symbols (1)
- `pkg.api.New`
The diff folds into change-set review: hunk-based
review sees only modified lines, so --base adds the symbols hunks miss — removed
and added public symbols, and breaking signature changes:
git diff | codemap review - --graph new.json --base old.jsonThe same is a serve op and an MCP diff tool (base = path to the baseline
graph.json; the session graph is the after), returning
{ok, added, removed, changes:[{symbol, kind, severity, detail}], summary} — the
structured "did this change break the API?" check an agent runs after an edit.
# fail the PR if it introduces a breaking API change vs the base branch
- run: codemap build ./yourpkg -o new.json
- run: git checkout "$BASE_SHA" && codemap build ./yourpkg -o old.json
- run: codemap diff old.json new.json --exit-codeDeterministic — same pair of graphs ⇒ same verdict.