codebase_impact
Identify every file and function that depends on a target file or symbol, showing the blast radius before refactoring or deleting code.
Instructions
Impact Analysis — return the BLAST RADIUS for a file or symbol. Lists every file (and, where helpful, function) that could break if you change the target. Polymorphic on target: a path-like string ('src/foo.ts') triggers file-mode; a name-like string ('validateUser') triggers symbol-mode. Use this BEFORE refactoring, renaming, or deleting code to know what depends on it.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectPath | No | Absolute path to the project directory. | |
| target | Yes | Target file path (relative) OR symbol name. | |
| depth | No | How many hops back to walk (default 3, max 10). |
Implementation Reference
- src/tools/graph-tools.ts:329-354 (handler)The handler case for 'codebase_impact' inside handleGraphTool(). Extracts target/depth args, loads the symbol graph cache, calls getImpactRadius(), and formats the blast-radius output.
case "codebase_impact": { const target = (args.target as string)?.trim(); if (!target) return "Missing required argument: target"; const depth = typeof args.depth === "number" ? args.depth : 3; const projectId = projectIdFromPath(projectPath); const cache = await getSymbolGraphCache(projectId); if (!cache) { return "No symbol graph found. Run codebase_graph_build (or codebase_index) first."; } const result = await getImpactRadius(cache, target, depth); const lines = [ `Blast radius for ${result.targetKind}: ${result.target}`, `Depth: ${result.depth} Total impacted files: ${result.totalFiles}`, "", ]; if (result.totalFiles === 0) { lines.push("No callers found — nothing else depends on this."); } else { for (const [hop, files] of result.filesByDepth.entries()) { lines.push(`Hop ${hop} (${files.length} files):`); for (const f of files) lines.push(` - ${f}`); lines.push(""); } } return lines.join("\n").trimEnd(); } - src/services/graph-impact.ts:30-95 (handler)The core impact analysis function getImpactRadius(). Does BFS over the reverseFileIndex to find all files that depend on a given target file or symbol, up to a configurable depth.
export async function getImpactRadius( cache: SymbolGraphCache, target: string, depth: number = 3, ): Promise<ImpactResult> { const safeDepth = Math.max(1, Math.min(depth, MAX_IMPACT_DEPTH)); const reverseIndex = await cache.getReverseFileIndex(); const targetKind: "file" | "symbol" = looksLikeFilePath(target) ? "file" : "symbol"; // Resolve to one or more "seed" files let seedFiles: Set<string>; if (targetKind === "file") { seedFiles = new Set([target]); } else { seedFiles = new Set(); const nameIndex = await cache.getNameIndex(); const refs = nameIndex.get(target) ?? []; for (const r of refs) seedFiles.add(r.file); } const visited = new Set<string>(); const filesByDepth = new Map<number, string[]>(); let frontier = new Set(seedFiles); for (const f of seedFiles) visited.add(f); let truncated = false; for (let hop = 1; hop <= safeDepth; hop++) { const next = new Set<string>(); for (const calleeFile of frontier) { const callers = reverseIndex.get(calleeFile); if (!callers) continue; for (const callerFile of callers) { if (visited.has(callerFile)) continue; next.add(callerFile); visited.add(callerFile); } } if (next.size === 0) break; filesByDepth.set(hop, Array.from(next).sort()); frontier = next; // After reaching the depth limit, check if more callers exist beyond it. if (hop === safeDepth) { for (const calleeFile of frontier) { const callers = reverseIndex.get(calleeFile); if (!callers) continue; for (const callerFile of callers) { if (!visited.has(callerFile)) { truncated = true; break; } } if (truncated) break; } } } let totalFiles = 0; for (const arr of filesByDepth.values()) totalFiles += arr.length; return { target, targetKind, depth: safeDepth, filesByDepth, totalFiles, truncated, }; } - src/index.ts:312-326 (registration)Tool registration on the MCP server using server.tool() with name 'codebase_impact', schema for projectPath/target/depth, and handler that delegates to handleGraphTool().
// ── Impact analysis (symbol-level call graph) ─────────────────────────── server.tool( "codebase_impact", "Impact Analysis — return the BLAST RADIUS for a file or symbol. Lists every file (and, where helpful, function) that could break if you change the target. Polymorphic on target: a path-like string ('src/foo.ts') triggers file-mode; a name-like string ('validateUser') triggers symbol-mode. Use this BEFORE refactoring, renaming, or deleting code to know what depends on it.", { projectPath: z.string().describe("Absolute path to the project directory.").optional(), target: z.string().describe("Target file path (relative) OR symbol name."), depth: z.number().describe("How many hops back to walk (default 3, max 10).").optional(), }, async (args) => ({ content: [{ type: "text", text: await handleGraphTool("codebase_impact", args) }], }), ); - src/tools/graph-tools.ts:8-15 (schema)Import of getImpactRadius from the graph-impact service (note: the actual function signature is an async function with cache, target, depth params returning ImpactResult).
import { type FlowNode, getCallFlow, getImpactRadius, getSymbolContext, listSymbols, looksLikeFilePath, } from "../services/graph-impact.js"; - src/constants.ts:132-133 (helper)Constant MAX_IMPACT_DEPTH = 10, defining the maximum BFS depth for codebase_impact queries.
/** Maximum BFS depth for `codebase_impact` (blast radius) queries. */ export const MAX_IMPACT_DEPTH = 10;