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

Method traverseBFS

src/graph/traversal.ts:48–145  ·  view source on GitHub ↗

* Traverse the graph using breadth-first search * * @param startId - Starting node ID * @param options - Traversal options * @returns Subgraph containing traversed nodes and edges

(startId: string, options: TraversalOptions = {})

Source from the content-addressed store, hash-verified

46 * @returns Subgraph containing traversed nodes and edges
47 */
48 traverseBFS(startId: string, options: TraversalOptions = {}): Subgraph {
49 const opts = { ...DEFAULT_OPTIONS, ...options };
50 const startNode = this.queries.getNodeById(startId);
51
52 if (!startNode) {
53 return { nodes: new Map(), edges: [], roots: [] };
54 }
55
56 const nodes = new Map<string, Node>();
57 const edges: Edge[] = [];
58 const visited = new Set<string>();
59 // Enqueue-once guard, tracked separately from `visited` (which is only set
60 // on dequeue). Guarding the enqueue on `visited` alone let a target
61 // reachable via two edges get queued twice; the second dequeue then hit
62 // `visited.has → continue` and its edge was never recorded, so parallel
63 // edges (A calls AND references B, or two `calls` on different lines — edges
64 // are unique on source+target+kind+line+col) went missing from the result
65 // (#1090). `enqueued` makes each node queued exactly once.
66 const enqueued = new Set<string>([startNode.id]);
67 // Edge-identity dedup so a `direction:'both'` scan — which encounters A→B
68 // from both endpoints — records each edge once.
69 const seenEdges = new Set<string>();
70 const edgeKey = (e: Edge) =>
71 `${e.source}|${e.target}|${e.kind}|${e.line ?? -1}|${e.column ?? -1}`;
72 const queue: TraversalStep[] = [{ node: startNode, edge: null, depth: 0 }];
73
74 if (opts.includeStart) {
75 nodes.set(startNode.id, startNode);
76 }
77
78 while (queue.length > 0 && nodes.size < opts.limit) {
79 const step = queue.shift()!;
80 const { node, depth } = step;
81
82 if (visited.has(node.id)) {
83 continue;
84 }
85 visited.add(node.id);
86
87 // Check depth limit
88 if (depth >= opts.maxDepth) {
89 continue;
90 }
91
92 // Get adjacent edges, prioritizing structural edges (contains, calls)
93 // over reference edges so BFS discovers internal structure before
94 // fanning out to external references (e.g., component usages in templates).
95 const adjacentEdges = this.getAdjacentEdges(node.id, opts.direction, opts.edgeKinds);
96 adjacentEdges.sort((a, b) => {
97 const priority = (e: Edge) => e.kind === 'contains' ? 0 : e.kind === 'calls' ? 1 : 2;
98 return priority(a) - priority(b);
99 });
100
101 // Batch-fetch neighbors we might newly enqueue in one query (was N+1 per
102 // BFS step). Already-queued/visited neighbors are already in `nodes`, so
103 // they don't need re-fetching to record an edge back to them.
104 const wantIds = adjacentEdges
105 .map((e) => (e.source === node.id ? e.target : e.source))

Callers 3

traverseMethod · 0.80
findRelevantContextMethod · 0.80
graph.test.tsFile · 0.80

Calls 6

getAdjacentEdgesMethod · 0.95
hasMethod · 0.80
getNodesByIdsMethod · 0.80
getNodeByIdMethod · 0.65
getMethod · 0.65
setMethod · 0.45

Tested by

no test coverage detected