Skip to content

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.

  • 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.
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).
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.

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": []
}
  • A nonexistent file is silent, not an error. It lands in unmatched_files alongside the real ones in matched_files — the call always succeeds; check unmatched_files yourself if you need to know a path didn’t resolve.
  • affected_components is empty, not missing, without architecture — and in these two example calls downstream_dependents/broken_contracts are also empty, because click’s parser only records extends/import edges (29 total across 572 entities, per parse’s num_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 an architecture and 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_depth bounds the reverse walk, not the file scan — raising it only affects how far downstream_dependents reaches, never which files get matched or which entities count as “changed”.
  • dep_graph and architecture are 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 — no session_id/type pair — so there’s nothing to pass into a follow-up call; re-run diff_impact itself, or resolve a fresh session, for a different query.
  • Like every non-graph/architecture-keyed tool response, a tight max_tokens degrades this result via generic enforce_budget (largest top-level key dropped repeatedly, then _budget_truncated: true), not the graph-aware progressive reduction.