Skip to content

Commit a42f5cb

Browse files
authored
explore: batch callerTests' hop 2-3 caller lookups (#574)
getCallersMany fetches depth-1 callers for a whole frontier in one incoming-edges query + one getNodesByIds instead of a getCallers round-trip per node; the same 64-node budget bounds each hop.
1 parent 77c8243 commit a42f5cb

3 files changed

Lines changed: 43 additions & 11 deletions

File tree

‎src/codegraph.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2823,6 +2823,14 @@ export class CodeGraph {
28232823
return this.traverser.getCallers(nodeId, maxDepth);
28242824
}
28252825

2826+
/**
2827+
* Depth-1 callers for many targets in two queries — the batch form of
2828+
* getCallers(id, 1), for bounded fan-in walks like callerTests.
2829+
*/
2830+
getCallersMany(nodeIds: readonly string[]): Array<{ node: Node; edge: Edge }> {
2831+
return this.traverser.getCallersMany(nodeIds);
2832+
}
2833+
28262834
/**
28272835
* Get callees of a function/method
28282836
*

‎src/graph/traversal.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,29 @@ export class GraphTraverser {
294294
return result;
295295
}
296296

297+
/**
298+
* Depth-1 callers for MANY targets in two round-trips — one incoming-edges
299+
* query + one getNodesByIds — instead of a getCallers(id, 1) pair per id.
300+
* Same kind list as getCallersRecursive; a caller repeats once per target it
301+
* calls, matching separate per-node calls, and once per target at most.
302+
*/
303+
getCallersMany(nodeIds: readonly string[]): Array<{ node: Node; edge: Edge }> {
304+
if (nodeIds.length === 0) return [];
305+
const incoming = this.queries.getIncomingEdgesTo(nodeIds, ['calls', 'references', 'imports', 'instantiates', 'navigates']);
306+
const nodes = this.queries.getNodesByIds(incoming.map((e) => e.source));
307+
const reported = new Set<string>();
308+
const out: Array<{ node: Node; edge: Edge }> = [];
309+
for (const edge of incoming) {
310+
const node = nodes.get(edge.source);
311+
if (!node) continue;
312+
const key = `${edge.target}${edge.source}`;
313+
if (reported.has(key)) continue;
314+
reported.add(key);
315+
out.push({ node, edge });
316+
}
317+
return out;
318+
}
319+
297320
private getCallersRecursive(
298321
nodeId: string,
299322
maxDepth: number,

‎src/mcp/tools.ts‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4559,17 +4559,18 @@ export class ToolHandler {
45594559
for (let hop = 2; hop <= MAX_HOPS && frontier.length > 0 && budget > 0; hop++) {
45604560
const next: Array<{ node: Node; edge: Edge }> = [];
45614561
const found: Array<{ node: Node; edge: Edge }> = [];
4562-
for (const { node } of frontier) {
4563-
if (budget-- <= 0) break;
4564-
let callers: Array<{ node: Node; edge: Edge }> = [];
4565-
try { callers = cg.getCallers(node.id); } catch { continue; }
4566-
for (const c of callers) {
4567-
const n = c?.node;
4568-
if (!n || visited.has(n.id)) continue;
4569-
visited.add(n.id);
4570-
if (isTestPath(n.filePath)) found.push(c);
4571-
else next.push(c);
4572-
}
4562+
// One batched depth-1 callers query per hop instead of a getCallers
4563+
// round-trip per frontier node; the same 64-node budget bounds it.
4564+
const ids = frontier.slice(0, budget).map((f) => f.node.id);
4565+
budget -= ids.length;
4566+
let pairs: Array<{ node: Node; edge: Edge }> = [];
4567+
try { pairs = cg.getCallersMany(ids); } catch { pairs = []; }
4568+
for (const c of pairs) {
4569+
const n = c?.node;
4570+
if (!n || visited.has(n.id)) continue;
4571+
visited.add(n.id);
4572+
if (isTestPath(n.filePath)) found.push(c);
4573+
else next.push(c);
45734574
}
45744575
if (found.length > 0) return { callers: found, exhausted: budget <= 0 };
45754576
frontier = next;

0 commit comments

Comments
 (0)