Blast Radius β
Blast radius answers one question: "if I change this symbol, what else breaks?" It is the single most useful query before any code modification, and the reason the get_blast_radius tool is listed as mandatory in the AI Assistant Rules.
Why it exists β
Grepping for a name gives you textual uses. What you actually want is the transitive set of dependents, each tagged with how confident Ctxo is that the break will be real. That is blast radius.
The algorithm lives in packages/cli/src/core/blast-radius/blast-radius-calculator.ts.
The algorithm β
- Start at the target
symbolId. - BFS backwards along
getReverseEdges(who points to this node). - For each dependent node, group the incoming edges and pick the strongest confidence among them. A node reached by both
usesandcallsis classifiedconfirmed. - Record
depth(BFS layer), the set of edge kinds that hit it, and ariskScoreof1 / depth^0.7so deeper dependents count for less. - Apply the co-change boost (see below).
- After BFS, recompute
dependentCountas in-degree within the blast set (not the whole graph) so you see how interconnected the blast itself is. - Aggregate into
overallRiskScore, capped at 1.
Confidence tiers β
| Tier | Edge kinds | Meaning |
|---|---|---|
confirmed | calls, extends, implements | Structural dependency. Breaks are mechanical. |
likely | uses | Reference without a call site. Often breaks. |
potential | imports | Module-level reach only. May be unused. |
When to trust which tier
confirmedalways acts on it. These break at compile time.likelyread the dependent before ruling it out.potentialskim for surprises. Import-only means "the file pulled you in", not that your symbol is exercised.
Co-change boost β
Ctxo also consults a co-change matrix built from git history (see core/co-change/co-change-analyzer.ts). If two files change together in more than 50 percent of commits that touch either, a potential link between symbols in those files is upgraded to likely. This catches the case where a symbol is not structurally linked but always modified together in practice.
The coChangeFrequency is reported on each entry so you can see why the upgrade happened.
Risk score β
Per-entry: round(1 / depth^0.7, 3). Overall:
overallRiskScore = min( sum(riskScore) / max(directDependents, 1) , 1 )The normalization by direct-dependent count means a symbol with one direct dependent but a long tail gets a lower overall score than a symbol with ten direct dependents. This matches intuition: wide blast is riskier than deep blast.
Reading the response β
{
"impactedSymbols": [
{
"symbolId": "src/api/handler.ts::handleRequest::function",
"depth": 1,
"dependentCount": 3,
"riskScore": 1.0,
"confidence": "confirmed",
"edgeKinds": ["calls"]
}
],
"directDependentsCount": 1,
"confirmedCount": 1,
"likelyCount": 0,
"potentialCount": 0,
"overallRiskScore": 1.0
}directDependentsCountis your headline number for PR review.confirmedCountalone tells you how many things will definitely need touching.
When to use it β
Call before any non-trivial edit. Pair with get_why_context to check whether the symbol already has a revert history (high blast + prior reverts = proceed carefully).
Related β
get_blast_radiusthe MCP tool.get_pr_impactruns blast radius for every changed symbol in a PR.- Blast radius comparison how Ctxo's tiered model compares to naive text-grep or IDE "find references".
Implementation detail
The exact exponent (0.7) and co-change threshold (0.5) are chosen empirically. See blast-radius-calculator.ts for the current constants.