Grep tells you where a name appears. It doesn't tell you the transitive blast radius, and it can't tell you whether your change closed a dependency loop three call-hops away. The call graph can — deterministically, so the same answer goes in the PR every time.
The fixture (examples/scenarios/refactor) ships a small service in two states,
before/ and after/, where the "after" refactor adds an audit hook
that accidentally creates a cycle. Real commands, real output.
Measure the blast radius before you touch it
impact reports the transitive callers and callees of
a function and how many modules they span — the "if I break this, who feels it" number.
Now the PR description writes itself: "Changes persist — 4 transitive
callers across 3 modules. Reviewers from those areas, please look." That one line
is what makes a reviewer slow down at the right moment.
Catch the cycle the refactor introduced
Snapshot the graph before and after as canonical JSON, then diff
them. The refactor added an audit() hook that calls back into
persist() — a brand-new circular dependency. The gate refuses to let it
through.
The reviewer sees the health grade slip C → D+, the exact new cycle, and the two edges that caused it — rendered as a Markdown comment right in the PR. Nobody has to notice the loop by hand; the gate did.
What you got. A pre-flight blast-radius number
(4 transitive callers across 3 modules) that tells reviewers where to
look, and a deterministic PR gate that caught a newly introduced cycle
— persist → audit → persist — and failed the build (exit 2) before it
merged. Refactoring stops being a leap of faith.
Every block above is verbatim output from the commands run against
examples/scenarios/refactor. Wire diff into a GitHub Action to
post the report as a PR comment automatically — see
docs/integrations.md.