DEV Community

Avery Lin
Avery Lin

Posted on

Opinion: A Free Model Can Map a Patch's Blast Radius — Your Diff Review Can't

A diff review answers what changed, but it cannot answer what the change touches. An AI patch can alter one function while silently shifting the behavior of every caller two modules away, and no line-level review will catch that connection. The blast radius of a patch lives in the call graph, not in the diff, and that graph is exactly what free model access is best suited to produce. My position is simple: before you approve an AI patch, make a free model map its blast radius, then verify that map against a static analyzer.

The diff is a lie about risk

A one-line signature change looks trivial in the diff and catastrophic in the call graph. A new branch in a utility function changes the data flow for every module that imports it, and the tests will stay green because they exercise the old paths. The diff review optimizes for what the patch touched, while the real risk sits in what the patch reaches. That gap grows with AI patches, because the model optimizes for the tests it can see, not for the callers it cannot.

This is not another checklist for reading a diff; it is a different artifact entirely. A diff autopsy tells you what was deleted or churned, but it does not tell you which entry points can now reach the changed code. The call graph is the missing layer between the patch and the production behavior, and it is the layer where free model economics actually make sense. Generation is cheap, but a structured inventory of affected paths is expensive to produce by hand, which is why most teams skip it.

The blast radius workflow

The workflow treats the free model as a mapping assistant and the static analyzer as the referee. Run the steps in order, and keep the outputs in the pull request so the review argues with evidence instead of vibes.

  1. Generate the patch with a free model. Keep the change small enough to reason about, and resist the urge to generate alternatives. The goal is one patch to map, not a menu of patches to compare. (Disclosure: This article was prepared as part of MonkeyCode's product outreach, and MonkeyCode's free model access is one way to run this step without spending budget.)

  2. Feed the patch and the repository structure to the model. Ask for a structured inventory of every function, caller, entry point, and data flow that the patch can affect. Do not ask for a summary; summaries hide the paths you need.

  3. Run a static analyzer to extract the real call graph. The script in this article covers Python, and the same idea transfers to TypeScript with ts-morph or to any language with a parser. The analyzer has no opinion; it only reports what the code structurally contains.

  4. Diff the model's map against the analyzer's graph. The gap between them is the review payload. Paths the analyzer confirms but the model missed are the highest-risk findings, because they represent blind spots in the model's reasoning.

  5. Write targeted tests for the confirmed high-risk paths. Use a free server option to run those tests without touching shared infrastructure, and attach the results to the blast radius report. The map tells you which tests to write; it does not replace the tests themselves.

The prompt template

The prompt below is the exact structure I use when asking a free model for a blast radius inventory. It forces the model to produce a list instead of a paragraph, and it demands a confidence level for every path so you know where to focus verification.

You are reviewing a patch. Here is the diff:
<PASTE_DIFF>

Here is the repository structure:
<PASTE_TREE_OR_FILE_LIST>

List every function and module that can be affected by this patch, directly or transitively. For each affected function, name its callers, the entry points that reach it, and the data that flows through the change. Mark each path as HIGH, MEDIUM, or LOW confidence. Do not summarize the patch. Produce a structured inventory with one path per line.
Enter fullscreen mode Exit fullscreen mode

The confidence labels are the part most people skip, and they are the part that makes the output useful. A HIGH-confidence path that the analyzer confirms is a test target. A LOW-confidence path that the analyzer cannot find is either a dynamic call or a hallucination, and both deserve a human look.

The verification script

The script below extracts a minimal call graph from Python files using the standard library ast module. It is deliberately simple, because the goal is a fast cross-check, not a production-grade analyzer. Save it as callgraph.py and run it against the files the patch touches.

#!/usr/bin/env python3
"""Extract a minimal call graph from Python files."""
import ast
import json
import sys


def extract(paths):
    graph = {"defined": [], "calls": {}}
    for path in paths:
        with open(path) as fh:
            tree = ast.parse(fh.read(), filename=path)
        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                graph["defined"].append(f"{path}:{node.lineno}:{node.name}")
                graph["calls"][node.name] = []
                for child in ast.walk(node):
                    if isinstance(child, ast.Call):
                        func = child.func
                        if isinstance(func, ast.Name):
                            graph["calls"][node.name].append(func.id)
                        elif isinstance(func, ast.Attribute):
                            graph["calls"][node.name].append(func.attr)
    return graph


if __name__ == "__main__":
    print(json.dumps(extract(sys.argv[1:]), indent=2))
Enter fullscreen mode Exit fullscreen mode

Run it with python callgraph.py changed_file.py, then compare the output with the model's inventory. The comparison is a three-way check: the model's HIGH paths that appear in the graph are confirmed risk, the graph entries the model never mentioned are missed risk, and the model's paths that the graph cannot confirm are unverified risk.

Reading the gap like an engineer

The table below is the decision rule I use when comparing the model's map with the analyzer's graph. It replaces "this patch feels risky" with a concrete classification of each path.

Comparison result What it means Action
Model says HIGH, analyzer confirms Confirmed risk Write a targeted test before merge
Analyzer finds it, model never mentioned Model blind spot Review this path first; it is the most dangerous finding
Model says HIGH, analyzer cannot find it Hallucination or dynamic call Inspect manually; if unverifiable, treat as unknown risk
Both say LOW Low priority Note it and move on

The gap analysis is the actual deliverable. A model that misses half the callers is not useless; it is a signal that the patch reaches code the model did not understand, which is exactly the information a reviewer needs. The static analyzer is not a replacement for the model either, because it cannot tell you which paths matter semantically. The two tools correct each other's blind spots.

Who should skip this approach

Teams working in heavily dynamic codebases will find the static analyzer too noisy to be useful, because reflection and dynamic dispatch hide most of the real call graph. Teams without a parser for their language will need to build or buy one, and the setup cost may exceed the benefit for small projects. Teams that cannot paste repository structure into a free model due to data residency rules should run this workflow with a local model or skip it entirely.

The approach also assumes the patch is small enough that the model can hold the relevant context in one pass. For a week-long refactor, run the mapping per commit rather than on the whole branch, so the gap analysis points at the exact commit that introduced the unexpected path. The blast radius map is a gate, not a guarantee, and it should sit alongside mutation testing and differential fuzzing rather than replace them.

The blast radius map is not a replacement for tests; it is a replacement for guessing which tests to write. A free model can produce the map in minutes, and a static analyzer can check it in seconds, and the gap between them is the most honest review comment you will ever get. Next time a free model hands you a patch, spend the first ten minutes on the call graph instead of the summary. The merge review will argue with itself less.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Top comments (0)