diff_impact
Point it at a set of changed files and it maps them to affected entities, recovered components, the transitive closure of who depends on them, and which changed entities form a public contract for outside callers — a blast-radius report without reading the diff itself.
When to call
Section titled “When to call”- Before editing, to see the blast radius of the files about to change.
- After editing, to check whether the diff crosses a public-contract boundary.
- When reviewing a PR without reading the full diff — matched/unmatched files plus downstream dependents summarize it.
Parameters
Section titled “Parameters”| Name | Type | Default | Description |
|---|---|---|---|
dep_graph |
DependencyGraph |
required | Dependency graph to analyze against. |
changed_files |
list[str] |
required | Changed file paths, e.g. from git diff --name-only. |
architecture |
Architecture | None |
None |
Optional recovered architecture, used to map changed entities to component names. |
max_depth |
int |
3 |
Maximum reverse-dependency hops to walk (1 = direct callers only). |
Returns
Section titled “Returns”| Field | Type | Meaning |
|---|---|---|
changed_files |
list[str] |
Echoes the input list verbatim. |
matched_files |
list[str] |
Subset of changed_files that matched at least one graph entity. |
unmatched_files |
list[str] |
changed_files entries with no matching entity — path typos, non-code files, or files outside the parsed graph. |
num_changed_entities |
int |
Count of changed_entities. |
changed_entities |
list[dict] |
{fqn, name, kind, file_path} for every entity in a matched file. |
affected_components |
list[str] |
Sorted recovered-component names containing a changed entity — empty if no architecture was passed. |
downstream_dependents |
list[dict] |
Reverse-dependency closure: entities that transitively depend on a changed entity, up to max_depth hops; each node carries fqn, distance, via_relation (same shape dependency_cone uses for its cone blocks). |
num_downstream |
int |
len(downstream_dependents). |
broken_contracts |
list[dict] |
Changed entities with incoming edges from outside the changed-file set — {fqn, dependents (capped at 20), num_dependents} — the changed entities an external caller actually depends on. |
Examples
Section titled “Examples”Both calls run against the same parse session as the other star-tool pages
(dep_graph="fe538dededcc", click’s src/click/ tree, 572 entities / 29 edges).
→ diff_impact(dep_graph="fe538dededcc", changed_files=["src/click/exceptions.py"], architecture="7abc279063c7", max_depth=2){ "changed_files": ["src/click/exceptions.py"], "matched_files": ["src/click/exceptions.py"], "unmatched_files": [], "num_changed_entities": 35, "changed_entities": [ { "fqn": "click.exceptions.Abort", "name": "Abort", "kind": "class", "file_path": "click/exceptions.py" } // … 34 more entities, all in click/exceptions.py ], "affected_components": ["Exceptions"], "downstream_dependents": [], "num_downstream": 0, "broken_contracts": []}The second call mixes a real path with a made-up one, to show the matched/unmatched split
without an architecture argument:
→ diff_impact(dep_graph="fe538dededcc", changed_files=["src/click/shell_completion.py", "src/click/does_not_exist.py"], max_depth=1){ "changed_files": ["src/click/shell_completion.py", "src/click/does_not_exist.py"], "matched_files": ["src/click/shell_completion.py"], "unmatched_files": ["src/click/does_not_exist.py"], "num_changed_entities": 36, "changed_entities": [ /* … 36 entities, all in click/shell_completion.py … */ ], "affected_components": [], "downstream_dependents": [], "num_downstream": 0, "broken_contracts": []}Things worth knowing
Section titled “Things worth knowing”- A nonexistent file is silent, not an error. It lands in
unmatched_filesalongside the real ones inmatched_files— the call always succeeds; checkunmatched_filesyourself if you need to know a path didn’t resolve. affected_componentsis empty, not missing, withoutarchitecture— and in these two example callsdownstream_dependents/broken_contractsare also empty, because click’s parser only recordsextends/importedges (29 total across 572 entities, perparse’snum_edges), so neither run’s changed files were extended or imported by anything else in this repo. Don’t treat any of these empty lists as “nothing affected” unless you actually passed anarchitectureand know the repo’s edges are rich enough to populate the closure — a repo with a fuller call graph would populate both, so the empty result here is a fact about click, not a bug in the tool.max_depthbounds the reverse walk, not the file scan — raising it only affects how fardownstream_dependentsreaches, never which files get matched or which entities count as “changed”.dep_graphandarchitectureare consumed by session ID, but nothing new is stored. The MCP wrapper resolves both from the session store before calling the underlying function, then returns a plain dict — nosession_id/typepair — so there’s nothing to pass into a follow-up call; re-rundiff_impactitself, or resolve a fresh session, for a different query.- Like every non-
graph/architecture-keyed tool response, a tightmax_tokensdegrades this result via genericenforce_budget(largest top-level key dropped repeatedly, then_budget_truncated: true), not the graph-aware progressive reduction.