MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / buildBlastRadiusSection

Method buildBlastRadiusSection

src/mcp/tools.ts:2440–2490  ·  view source on GitHub ↗

* Compact "blast radius" for the entry symbols of an explore result: who * depends on each (callers) and which test files cover it — LOCATIONS ONLY, * no source, so the agent knows what to update / re-verify before editing * without reaching for a separate impact call. Always-on, but skips

(cg: CodeGraph, subgraph: Subgraph)

Source from the content-addressed store, hash-verified

2438 * qualify so a leaf-only exploration stays clean.
2439 */
2440 private buildBlastRadiusSection(cg: CodeGraph, subgraph: Subgraph): string {
2441 const ROOT_CAP = 5; // only the symbols the query actually targeted
2442 const FILE_CAP = 4; // caller files listed per symbol before "+N more"
2443 const MEANINGFUL = new Set<string>([
2444 'function', 'method', 'class', 'interface', 'struct', 'trait', 'protocol',
2445 'enum', 'type_alias', 'component', 'constant', 'variable', 'property', 'field',
2446 ]);
2447 const rel = (p: string) => p.replace(/\\/g, '/');
2448
2449 const roots = subgraph.roots
2450 .map((id) => subgraph.nodes.get(id))
2451 .filter((n): n is Node => !!n && MEANINGFUL.has(n.kind))
2452 .slice(0, ROOT_CAP);
2453 if (roots.length === 0) return '';
2454
2455 const entries: string[] = [];
2456 for (const root of roots) {
2457 let callers: Array<{ node: Node }> = [];
2458 try { callers = cg.getCallers(root.id) as Array<{ node: Node }>; } catch { /* skip this root */ }
2459
2460 const seen = new Set<string>();
2461 const uniq: Node[] = [];
2462 for (const c of callers) {
2463 if (c?.node && !seen.has(c.node.id)) { seen.add(c.node.id); uniq.push(c.node); }
2464 }
2465 if (uniq.length === 0) continue; // no blast radius → nothing to flag
2466
2467 const callerFiles = [...new Set(uniq.map((n) => rel(n.filePath)))];
2468 const testFiles = callerFiles.filter((f) => isTestFile(f));
2469 const nonTest = callerFiles.filter((f) => !isTestFile(f));
2470
2471 const shown = nonTest.slice(0, FILE_CAP).map((f) => `\`${f}\``).join(', ');
2472 const more = nonTest.length > FILE_CAP ? ` +${nonTest.length - FILE_CAP} more` : '';
2473 const where = nonTest.length > 0 ? ` in ${shown}${more}` : '';
2474 const tests = testFiles.length > 0
2475 ? `; tests: ${testFiles.slice(0, FILE_CAP).map((f) => `\`${f}\``).join(', ')}${testFiles.length > FILE_CAP ? ` +${testFiles.length - FILE_CAP}` : ''}`
2476 : this.indirectTestNote(cg, uniq, rel);
2477
2478 entries.push(
2479 `- \`${root.name}\` (${rel(root.filePath)}:${root.startLine}) — ${uniq.length} caller${uniq.length === 1 ? '' : 's'}${where}${tests}`,
2480 );
2481 }
2482 if (entries.length === 0) return '';
2483
2484 return [
2485 '**Blast radius — what depends on these (update/verify before editing)**',
2486 '',
2487 ...entries,
2488 '',
2489 ].join('\n');
2490 }
2491
2492 /**
2493 * Test-coverage note for a blast-radius entry whose DIRECT callers include no

Callers 1

handleExploreMethod · 0.95

Calls 6

indirectTestNoteMethod · 0.95
isTestFileFunction · 0.90
hasMethod · 0.80
getMethod · 0.65
getCallersMethod · 0.45
joinMethod · 0.45

Tested by

no test coverage detected