@saluzi/codegraph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/bin/codegraph.d.ts +25 -0
  2. package/dist/bin/node-version-check.d.ts +37 -0
  3. package/dist/bin/uninstall.d.ts +14 -0
  4. package/dist/context/formatter.d.ts +33 -0
  5. package/dist/context/formatter.js +244 -0
  6. package/dist/context/index.d.ts +117 -0
  7. package/dist/context/index.js +1050 -0
  8. package/dist/db/index.d.ts +101 -0
  9. package/dist/db/index.js +250 -0
  10. package/dist/db/migrations.d.ts +47 -0
  11. package/dist/db/migrations.js +131 -0
  12. package/dist/db/queries.d.ts +291 -0
  13. package/dist/db/queries.js +1349 -0
  14. package/dist/db/schema.sql +151 -0
  15. package/dist/db/sqlite-adapter.d.ts +49 -0
  16. package/dist/db/sqlite-adapter.js +141 -0
  17. package/dist/directory.d.ts +62 -0
  18. package/dist/directory.js +264 -0
  19. package/dist/errors.d.ts +149 -0
  20. package/dist/errors.js +219 -0
  21. package/dist/extraction/dfm-extractor.d.ts +31 -0
  22. package/dist/extraction/dfm-extractor.js +151 -0
  23. package/dist/extraction/grammars.d.ts +94 -0
  24. package/dist/extraction/grammars.js +357 -0
  25. package/dist/extraction/index.d.ts +148 -0
  26. package/dist/extraction/index.js +1286 -0
  27. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  28. package/dist/extraction/languages/c-cpp.js +126 -0
  29. package/dist/extraction/languages/csharp.d.ts +3 -0
  30. package/dist/extraction/languages/csharp.js +72 -0
  31. package/dist/extraction/languages/dart.d.ts +3 -0
  32. package/dist/extraction/languages/dart.js +192 -0
  33. package/dist/extraction/languages/go.d.ts +3 -0
  34. package/dist/extraction/languages/go.js +58 -0
  35. package/dist/extraction/languages/index.d.ts +10 -0
  36. package/dist/extraction/languages/index.js +49 -0
  37. package/dist/extraction/languages/java.d.ts +3 -0
  38. package/dist/extraction/languages/java.js +64 -0
  39. package/dist/extraction/languages/javascript.d.ts +3 -0
  40. package/dist/extraction/languages/javascript.js +90 -0
  41. package/dist/extraction/languages/kotlin.d.ts +3 -0
  42. package/dist/extraction/languages/kotlin.js +253 -0
  43. package/dist/extraction/languages/lua.d.ts +3 -0
  44. package/dist/extraction/languages/lua.js +150 -0
  45. package/dist/extraction/languages/luau.d.ts +3 -0
  46. package/dist/extraction/languages/luau.js +37 -0
  47. package/dist/extraction/languages/pascal.d.ts +3 -0
  48. package/dist/extraction/languages/pascal.js +66 -0
  49. package/dist/extraction/languages/php.d.ts +3 -0
  50. package/dist/extraction/languages/php.js +107 -0
  51. package/dist/extraction/languages/python.d.ts +3 -0
  52. package/dist/extraction/languages/python.js +56 -0
  53. package/dist/extraction/languages/ruby.d.ts +3 -0
  54. package/dist/extraction/languages/ruby.js +114 -0
  55. package/dist/extraction/languages/rust.d.ts +3 -0
  56. package/dist/extraction/languages/rust.js +109 -0
  57. package/dist/extraction/languages/scala.d.ts +3 -0
  58. package/dist/extraction/languages/scala.js +139 -0
  59. package/dist/extraction/languages/swift.d.ts +3 -0
  60. package/dist/extraction/languages/swift.js +91 -0
  61. package/dist/extraction/languages/typescript.d.ts +3 -0
  62. package/dist/extraction/languages/typescript.js +129 -0
  63. package/dist/extraction/liquid-extractor.d.ts +52 -0
  64. package/dist/extraction/liquid-extractor.js +313 -0
  65. package/dist/extraction/parse-worker.d.ts +8 -0
  66. package/dist/extraction/parse-worker.js +94 -0
  67. package/dist/extraction/svelte-extractor.d.ts +56 -0
  68. package/dist/extraction/svelte-extractor.js +272 -0
  69. package/dist/extraction/tree-sitter-helpers.d.ts +39 -0
  70. package/dist/extraction/tree-sitter-helpers.js +103 -0
  71. package/dist/extraction/tree-sitter-types.d.ts +191 -0
  72. package/dist/extraction/tree-sitter-types.js +10 -0
  73. package/dist/extraction/tree-sitter.d.ts +238 -0
  74. package/dist/extraction/tree-sitter.js +2430 -0
  75. package/dist/extraction/vue-extractor.d.ts +36 -0
  76. package/dist/extraction/vue-extractor.js +163 -0
  77. package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
  78. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  79. package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
  80. package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
  81. package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  82. package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  83. package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  84. package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
  85. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  86. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  87. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  88. package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
  89. package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  90. package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
  91. package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
  92. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  93. package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
  94. package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  95. package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  96. package/dist/extraction/wasm-runtime-flags.d.ts +46 -0
  97. package/dist/extraction/wasm-runtime-flags.js +105 -0
  98. package/dist/graph/index.d.ts +8 -0
  99. package/dist/graph/index.js +13 -0
  100. package/dist/graph/queries.d.ts +109 -0
  101. package/dist/graph/queries.js +366 -0
  102. package/dist/graph/traversal.d.ts +137 -0
  103. package/dist/graph/traversal.js +528 -0
  104. package/dist/index.d.ts +509 -0
  105. package/dist/index.js +800 -0
  106. package/dist/installer/claude-md-template.d.ts +19 -0
  107. package/dist/installer/config-writer.d.ts +29 -0
  108. package/dist/installer/index.d.ts +140 -0
  109. package/dist/installer/instructions-template.d.ts +30 -0
  110. package/dist/installer/targets/claude.d.ts +55 -0
  111. package/dist/installer/targets/codex.d.ts +18 -0
  112. package/dist/installer/targets/cursor.d.ts +35 -0
  113. package/dist/installer/targets/hermes.d.ts +18 -0
  114. package/dist/installer/targets/opencode.d.ts +30 -0
  115. package/dist/installer/targets/registry.d.ts +38 -0
  116. package/dist/installer/targets/shared.d.ts +92 -0
  117. package/dist/installer/targets/toml.d.ts +64 -0
  118. package/dist/installer/targets/types.d.ts +122 -0
  119. package/dist/mcp/index.d.ts +98 -0
  120. package/dist/mcp/server-instructions.d.ts +20 -0
  121. package/dist/mcp/tools.d.ts +269 -0
  122. package/dist/mcp/transport.d.ts +117 -0
  123. package/dist/resolution/frameworks/cargo-workspace.d.ts +20 -0
  124. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  125. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  126. package/dist/resolution/frameworks/csharp.js +213 -0
  127. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  128. package/dist/resolution/frameworks/drupal.js +335 -0
  129. package/dist/resolution/frameworks/express.d.ts +8 -0
  130. package/dist/resolution/frameworks/express.js +225 -0
  131. package/dist/resolution/frameworks/go.d.ts +8 -0
  132. package/dist/resolution/frameworks/go.js +158 -0
  133. package/dist/resolution/frameworks/index.d.ts +52 -0
  134. package/dist/resolution/frameworks/index.js +137 -0
  135. package/dist/resolution/frameworks/java.d.ts +8 -0
  136. package/dist/resolution/frameworks/java.js +177 -0
  137. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  138. package/dist/resolution/frameworks/laravel.js +248 -0
  139. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  140. package/dist/resolution/frameworks/nestjs.js +374 -0
  141. package/dist/resolution/frameworks/python.d.ts +10 -0
  142. package/dist/resolution/frameworks/python.js +278 -0
  143. package/dist/resolution/frameworks/react.d.ts +8 -0
  144. package/dist/resolution/frameworks/react.js +272 -0
  145. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  146. package/dist/resolution/frameworks/ruby.js +198 -0
  147. package/dist/resolution/frameworks/rust.d.ts +8 -0
  148. package/dist/resolution/frameworks/rust.js +207 -0
  149. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  150. package/dist/resolution/frameworks/svelte.js +249 -0
  151. package/dist/resolution/frameworks/swift.d.ts +10 -0
  152. package/dist/resolution/frameworks/swift.js +376 -0
  153. package/dist/resolution/frameworks/vue.d.ts +9 -0
  154. package/dist/resolution/frameworks/vue.js +306 -0
  155. package/dist/resolution/import-resolver.d.ts +61 -0
  156. package/dist/resolution/import-resolver.js +663 -0
  157. package/dist/resolution/index.d.ts +118 -0
  158. package/dist/resolution/index.js +744 -0
  159. package/dist/resolution/lru-cache.d.ts +24 -0
  160. package/dist/resolution/lru-cache.js +62 -0
  161. package/dist/resolution/name-matcher.d.ts +50 -0
  162. package/dist/resolution/name-matcher.js +384 -0
  163. package/dist/resolution/path-aliases.d.ts +72 -0
  164. package/dist/resolution/path-aliases.js +238 -0
  165. package/dist/resolution/strip-comments.d.ts +40 -0
  166. package/dist/resolution/strip-comments.js +441 -0
  167. package/dist/resolution/types.d.ts +181 -0
  168. package/dist/resolution/types.js +8 -0
  169. package/dist/search/query-parser.d.ts +61 -0
  170. package/dist/search/query-parser.js +177 -0
  171. package/dist/search/query-utils.d.ts +59 -0
  172. package/dist/search/query-utils.js +383 -0
  173. package/dist/sync/git-hooks.d.ts +54 -0
  174. package/dist/sync/git-hooks.js +223 -0
  175. package/dist/sync/index.d.ts +25 -0
  176. package/dist/sync/index.js +28 -0
  177. package/dist/sync/watch-policy.d.ts +51 -0
  178. package/dist/sync/watch-policy.js +124 -0
  179. package/dist/sync/watcher.d.ts +83 -0
  180. package/dist/sync/watcher.js +192 -0
  181. package/dist/types.d.ts +433 -0
  182. package/dist/types.js +75 -0
  183. package/dist/ui/glyphs.d.ts +42 -0
  184. package/dist/ui/shimmer-progress.d.ts +11 -0
  185. package/dist/ui/shimmer-worker.d.ts +2 -0
  186. package/dist/ui/types.d.ts +20 -0
  187. package/dist/utils.d.ts +231 -0
  188. package/dist/utils.js +549 -0
  189. package/package.json +24 -0
@@ -0,0 +1,528 @@
1
+ "use strict";
2
+ /**
3
+ * Graph Traversal Algorithms
4
+ *
5
+ * BFS and DFS traversal for the code knowledge graph.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.GraphTraverser = void 0;
9
+ /**
10
+ * Default traversal options
11
+ */
12
+ const DEFAULT_OPTIONS = {
13
+ maxDepth: Infinity,
14
+ edgeKinds: [],
15
+ nodeKinds: [],
16
+ direction: 'outgoing',
17
+ limit: 1000,
18
+ includeStart: true,
19
+ };
20
+ /**
21
+ * Graph traverser for BFS and DFS traversal
22
+ */
23
+ class GraphTraverser {
24
+ queries;
25
+ constructor(queries) {
26
+ this.queries = queries;
27
+ }
28
+ /**
29
+ * Traverse the graph using breadth-first search
30
+ *
31
+ * @param startId - Starting node ID
32
+ * @param options - Traversal options
33
+ * @returns Subgraph containing traversed nodes and edges
34
+ */
35
+ traverseBFS(startId, options = {}) {
36
+ const opts = { ...DEFAULT_OPTIONS, ...options };
37
+ const startNode = this.queries.getNodeById(startId);
38
+ if (!startNode) {
39
+ return { nodes: new Map(), edges: [], roots: [] };
40
+ }
41
+ const nodes = new Map();
42
+ const edges = [];
43
+ const visited = new Set();
44
+ const queue = [{ node: startNode, edge: null, depth: 0 }];
45
+ if (opts.includeStart) {
46
+ nodes.set(startNode.id, startNode);
47
+ }
48
+ while (queue.length > 0 && nodes.size < opts.limit) {
49
+ const step = queue.shift();
50
+ const { node, edge, depth } = step;
51
+ if (visited.has(node.id)) {
52
+ continue;
53
+ }
54
+ visited.add(node.id);
55
+ // Add edge to result
56
+ if (edge) {
57
+ edges.push(edge);
58
+ }
59
+ // Check depth limit
60
+ if (depth >= opts.maxDepth) {
61
+ continue;
62
+ }
63
+ // Get adjacent edges, prioritizing structural edges (contains, calls)
64
+ // over reference edges so BFS discovers internal structure before
65
+ // fanning out to external references (e.g., component usages in templates).
66
+ const adjacentEdges = this.getAdjacentEdges(node.id, opts.direction, opts.edgeKinds);
67
+ adjacentEdges.sort((a, b) => {
68
+ const priority = (e) => e.kind === 'contains' ? 0 : e.kind === 'calls' ? 1 : 2;
69
+ return priority(a) - priority(b);
70
+ });
71
+ // Batch-fetch the unvisited neighbors in one query (was N+1 per BFS step).
72
+ const wantIds = adjacentEdges
73
+ .map((e) => (e.source === node.id ? e.target : e.source))
74
+ .filter((id) => !visited.has(id));
75
+ const neighborNodes = wantIds.length > 0 ? this.queries.getNodesByIds(wantIds) : new Map();
76
+ for (const adjEdge of adjacentEdges) {
77
+ const nextNodeId = adjEdge.source === node.id ? adjEdge.target : adjEdge.source;
78
+ if (visited.has(nextNodeId))
79
+ continue;
80
+ const nextNode = neighborNodes.get(nextNodeId);
81
+ if (!nextNode)
82
+ continue;
83
+ if (opts.nodeKinds && opts.nodeKinds.length > 0 && !opts.nodeKinds.includes(nextNode.kind)) {
84
+ continue;
85
+ }
86
+ nodes.set(nextNode.id, nextNode);
87
+ queue.push({ node: nextNode, edge: adjEdge, depth: depth + 1 });
88
+ }
89
+ }
90
+ return {
91
+ nodes,
92
+ edges,
93
+ roots: [startId],
94
+ };
95
+ }
96
+ /**
97
+ * Traverse the graph using depth-first search
98
+ *
99
+ * @param startId - Starting node ID
100
+ * @param options - Traversal options
101
+ * @returns Subgraph containing traversed nodes and edges
102
+ */
103
+ traverseDFS(startId, options = {}) {
104
+ const opts = { ...DEFAULT_OPTIONS, ...options };
105
+ const startNode = this.queries.getNodeById(startId);
106
+ if (!startNode) {
107
+ return { nodes: new Map(), edges: [], roots: [] };
108
+ }
109
+ const nodes = new Map();
110
+ const edges = [];
111
+ const visited = new Set();
112
+ if (opts.includeStart) {
113
+ nodes.set(startNode.id, startNode);
114
+ }
115
+ this.dfsRecursive(startNode, 0, opts, nodes, edges, visited);
116
+ return {
117
+ nodes,
118
+ edges,
119
+ roots: [startId],
120
+ };
121
+ }
122
+ /**
123
+ * Recursive DFS helper
124
+ */
125
+ dfsRecursive(node, depth, opts, nodes, edges, visited) {
126
+ if (visited.has(node.id) || nodes.size >= opts.limit || depth >= opts.maxDepth) {
127
+ return;
128
+ }
129
+ visited.add(node.id);
130
+ // Get adjacent edges
131
+ const adjacentEdges = this.getAdjacentEdges(node.id, opts.direction, opts.edgeKinds);
132
+ // Batch-fetch unvisited neighbors (was N+1 per DFS step).
133
+ const wantIds = adjacentEdges
134
+ .map((e) => (e.source === node.id ? e.target : e.source))
135
+ .filter((id) => !visited.has(id));
136
+ const neighborNodes = wantIds.length > 0 ? this.queries.getNodesByIds(wantIds) : new Map();
137
+ for (const edge of adjacentEdges) {
138
+ const nextNodeId = edge.source === node.id ? edge.target : edge.source;
139
+ if (visited.has(nextNodeId))
140
+ continue;
141
+ const nextNode = neighborNodes.get(nextNodeId);
142
+ if (!nextNode)
143
+ continue;
144
+ // Apply node kind filter
145
+ if (opts.nodeKinds && opts.nodeKinds.length > 0 && !opts.nodeKinds.includes(nextNode.kind)) {
146
+ continue;
147
+ }
148
+ // Add node and edge to result
149
+ nodes.set(nextNode.id, nextNode);
150
+ edges.push(edge);
151
+ // Recurse
152
+ this.dfsRecursive(nextNode, depth + 1, opts, nodes, edges, visited);
153
+ }
154
+ }
155
+ /**
156
+ * Get adjacent edges based on direction
157
+ */
158
+ getAdjacentEdges(nodeId, direction, edgeKinds) {
159
+ const kinds = edgeKinds && edgeKinds.length > 0 ? edgeKinds : undefined;
160
+ if (direction === 'outgoing') {
161
+ return this.queries.getOutgoingEdges(nodeId, kinds);
162
+ }
163
+ else if (direction === 'incoming') {
164
+ return this.queries.getIncomingEdges(nodeId, kinds);
165
+ }
166
+ else {
167
+ // Both directions
168
+ const outgoing = this.queries.getOutgoingEdges(nodeId, kinds);
169
+ const incoming = this.queries.getIncomingEdges(nodeId, kinds);
170
+ return [...outgoing, ...incoming];
171
+ }
172
+ }
173
+ /**
174
+ * Find all callers of a function/method
175
+ *
176
+ * @param nodeId - ID of the function/method node
177
+ * @param maxDepth - Maximum depth to traverse (default: 1)
178
+ * @returns Array of nodes that call this function
179
+ */
180
+ getCallers(nodeId, maxDepth = 1) {
181
+ const result = [];
182
+ const visited = new Set();
183
+ this.getCallersRecursive(nodeId, maxDepth, 0, result, visited);
184
+ return result;
185
+ }
186
+ getCallersRecursive(nodeId, maxDepth, currentDepth, result, visited) {
187
+ if (currentDepth >= maxDepth || visited.has(nodeId)) {
188
+ return;
189
+ }
190
+ visited.add(nodeId);
191
+ const incomingEdges = this.queries.getIncomingEdges(nodeId, ['calls', 'references', 'imports']);
192
+ if (incomingEdges.length === 0)
193
+ return;
194
+ // Batch-fetch all caller nodes in one round-trip instead of one
195
+ // getNodeById per edge (was N+1 — meaningful on functions with many callers).
196
+ const sourceIds = incomingEdges.map((e) => e.source);
197
+ const callerNodes = this.queries.getNodesByIds(sourceIds);
198
+ for (const edge of incomingEdges) {
199
+ const callerNode = callerNodes.get(edge.source);
200
+ if (callerNode && !visited.has(callerNode.id)) {
201
+ result.push({ node: callerNode, edge });
202
+ this.getCallersRecursive(callerNode.id, maxDepth, currentDepth + 1, result, visited);
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Find all functions/methods called by a function
208
+ *
209
+ * @param nodeId - ID of the function/method node
210
+ * @param maxDepth - Maximum depth to traverse (default: 1)
211
+ * @returns Array of nodes called by this function
212
+ */
213
+ getCallees(nodeId, maxDepth = 1) {
214
+ const result = [];
215
+ const visited = new Set();
216
+ this.getCalleesRecursive(nodeId, maxDepth, 0, result, visited);
217
+ return result;
218
+ }
219
+ getCalleesRecursive(nodeId, maxDepth, currentDepth, result, visited) {
220
+ if (currentDepth >= maxDepth || visited.has(nodeId)) {
221
+ return;
222
+ }
223
+ visited.add(nodeId);
224
+ const outgoingEdges = this.queries.getOutgoingEdges(nodeId, ['calls', 'references', 'imports']);
225
+ if (outgoingEdges.length === 0)
226
+ return;
227
+ // Batch-fetch callee nodes (was N+1 — see getCallersRecursive note).
228
+ const targetIds = outgoingEdges.map((e) => e.target);
229
+ const calleeNodes = this.queries.getNodesByIds(targetIds);
230
+ for (const edge of outgoingEdges) {
231
+ const calleeNode = calleeNodes.get(edge.target);
232
+ if (calleeNode && !visited.has(calleeNode.id)) {
233
+ result.push({ node: calleeNode, edge });
234
+ this.getCalleesRecursive(calleeNode.id, maxDepth, currentDepth + 1, result, visited);
235
+ }
236
+ }
237
+ }
238
+ /**
239
+ * Get the call graph for a function (both callers and callees)
240
+ *
241
+ * @param nodeId - ID of the function/method node
242
+ * @param depth - Maximum depth in each direction (default: 2)
243
+ * @returns Subgraph containing the call graph
244
+ */
245
+ getCallGraph(nodeId, depth = 2) {
246
+ const focalNode = this.queries.getNodeById(nodeId);
247
+ if (!focalNode) {
248
+ return { nodes: new Map(), edges: [], roots: [] };
249
+ }
250
+ const nodes = new Map();
251
+ const edges = [];
252
+ // Add focal node
253
+ nodes.set(focalNode.id, focalNode);
254
+ // Get callers
255
+ const callers = this.getCallers(nodeId, depth);
256
+ for (const { node, edge } of callers) {
257
+ nodes.set(node.id, node);
258
+ edges.push(edge);
259
+ }
260
+ // Get callees
261
+ const callees = this.getCallees(nodeId, depth);
262
+ for (const { node, edge } of callees) {
263
+ nodes.set(node.id, node);
264
+ edges.push(edge);
265
+ }
266
+ return {
267
+ nodes,
268
+ edges,
269
+ roots: [nodeId],
270
+ };
271
+ }
272
+ /**
273
+ * Get the type hierarchy for a class/interface
274
+ *
275
+ * @param nodeId - ID of the class/interface node
276
+ * @returns Subgraph containing the type hierarchy
277
+ */
278
+ getTypeHierarchy(nodeId) {
279
+ const focalNode = this.queries.getNodeById(nodeId);
280
+ if (!focalNode) {
281
+ return { nodes: new Map(), edges: [], roots: [] };
282
+ }
283
+ const nodes = new Map();
284
+ const edges = [];
285
+ const visited = new Set();
286
+ // Add focal node
287
+ nodes.set(focalNode.id, focalNode);
288
+ // Get ancestors (what this extends/implements)
289
+ this.getTypeAncestors(nodeId, nodes, edges, visited);
290
+ // Get descendants (what extends/implements this)
291
+ this.getTypeDescendants(nodeId, nodes, edges, visited);
292
+ return {
293
+ nodes,
294
+ edges,
295
+ roots: [nodeId],
296
+ };
297
+ }
298
+ getTypeAncestors(nodeId, nodes, edges, visited) {
299
+ if (visited.has(nodeId)) {
300
+ return;
301
+ }
302
+ visited.add(nodeId);
303
+ const outgoingEdges = this.queries.getOutgoingEdges(nodeId, ['extends', 'implements']);
304
+ if (outgoingEdges.length === 0)
305
+ return;
306
+ const parents = this.queries.getNodesByIds(outgoingEdges.map((e) => e.target));
307
+ for (const edge of outgoingEdges) {
308
+ const parentNode = parents.get(edge.target);
309
+ if (parentNode && !nodes.has(parentNode.id)) {
310
+ nodes.set(parentNode.id, parentNode);
311
+ edges.push(edge);
312
+ this.getTypeAncestors(parentNode.id, nodes, edges, visited);
313
+ }
314
+ }
315
+ }
316
+ getTypeDescendants(nodeId, nodes, edges, visited) {
317
+ if (visited.has(nodeId)) {
318
+ return;
319
+ }
320
+ visited.add(nodeId);
321
+ const incomingEdges = this.queries.getIncomingEdges(nodeId, ['extends', 'implements']);
322
+ if (incomingEdges.length === 0)
323
+ return;
324
+ const children = this.queries.getNodesByIds(incomingEdges.map((e) => e.source));
325
+ for (const edge of incomingEdges) {
326
+ const childNode = children.get(edge.source);
327
+ if (childNode && !nodes.has(childNode.id)) {
328
+ nodes.set(childNode.id, childNode);
329
+ edges.push(edge);
330
+ this.getTypeDescendants(childNode.id, nodes, edges, visited);
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * Find all usages of a symbol
336
+ *
337
+ * @param nodeId - ID of the symbol node
338
+ * @returns Array of nodes and edges that reference this symbol
339
+ */
340
+ findUsages(nodeId) {
341
+ const result = [];
342
+ // Get all incoming edges (references, calls, type_of, etc.)
343
+ const incomingEdges = this.queries.getIncomingEdges(nodeId);
344
+ if (incomingEdges.length === 0)
345
+ return result;
346
+ // Batch-fetch source nodes (was N+1).
347
+ const sources = this.queries.getNodesByIds(incomingEdges.map((e) => e.source));
348
+ for (const edge of incomingEdges) {
349
+ const sourceNode = sources.get(edge.source);
350
+ if (sourceNode)
351
+ result.push({ node: sourceNode, edge });
352
+ }
353
+ return result;
354
+ }
355
+ /**
356
+ * Calculate the impact radius of a node
357
+ *
358
+ * Returns all nodes that could be affected by changes to this node.
359
+ *
360
+ * @param nodeId - ID of the node
361
+ * @param maxDepth - Maximum depth to traverse (default: 3)
362
+ * @returns Subgraph containing potentially impacted nodes
363
+ */
364
+ getImpactRadius(nodeId, maxDepth = 3) {
365
+ const focalNode = this.queries.getNodeById(nodeId);
366
+ if (!focalNode) {
367
+ return { nodes: new Map(), edges: [], roots: [] };
368
+ }
369
+ const nodes = new Map();
370
+ const edges = [];
371
+ const visited = new Set();
372
+ // Add focal node
373
+ nodes.set(focalNode.id, focalNode);
374
+ // Traverse incoming edges to find all dependents
375
+ this.getImpactRecursive(nodeId, maxDepth, 0, nodes, edges, visited);
376
+ return {
377
+ nodes,
378
+ edges,
379
+ roots: [nodeId],
380
+ };
381
+ }
382
+ getImpactRecursive(nodeId, maxDepth, currentDepth, nodes, edges, visited) {
383
+ if (currentDepth >= maxDepth || visited.has(nodeId)) {
384
+ return;
385
+ }
386
+ visited.add(nodeId);
387
+ // For container nodes (classes, interfaces, structs, etc.), also traverse
388
+ // into their children so that callers of contained methods appear in impact
389
+ const focalNode = this.queries.getNodeById(nodeId);
390
+ if (focalNode) {
391
+ const containerKinds = new Set(['class', 'interface', 'struct', 'trait', 'protocol', 'module', 'enum']);
392
+ if (containerKinds.has(focalNode.kind)) {
393
+ const containsEdges = this.queries.getOutgoingEdges(nodeId, ['contains']);
394
+ if (containsEdges.length > 0) {
395
+ const children = this.queries.getNodesByIds(containsEdges.map((e) => e.target));
396
+ for (const edge of containsEdges) {
397
+ const childNode = children.get(edge.target);
398
+ if (childNode && !visited.has(childNode.id)) {
399
+ nodes.set(childNode.id, childNode);
400
+ edges.push(edge);
401
+ // Recurse into children at the same depth (they're part of the same symbol)
402
+ this.getImpactRecursive(childNode.id, maxDepth, currentDepth, nodes, edges, visited);
403
+ }
404
+ }
405
+ }
406
+ }
407
+ }
408
+ // Get all incoming edges (things that depend on this node)
409
+ const incomingEdges = this.queries.getIncomingEdges(nodeId);
410
+ if (incomingEdges.length === 0)
411
+ return;
412
+ const sources = this.queries.getNodesByIds(incomingEdges.map((e) => e.source));
413
+ for (const edge of incomingEdges) {
414
+ const sourceNode = sources.get(edge.source);
415
+ if (sourceNode && !nodes.has(sourceNode.id)) {
416
+ nodes.set(sourceNode.id, sourceNode);
417
+ edges.push(edge);
418
+ this.getImpactRecursive(sourceNode.id, maxDepth, currentDepth + 1, nodes, edges, visited);
419
+ }
420
+ }
421
+ }
422
+ /**
423
+ * Find the shortest path between two nodes
424
+ *
425
+ * @param fromId - Starting node ID
426
+ * @param toId - Target node ID
427
+ * @param edgeKinds - Edge types to consider (all if empty)
428
+ * @returns Array of nodes and edges forming the path, or null if no path exists
429
+ */
430
+ findPath(fromId, toId, edgeKinds = []) {
431
+ const fromNode = this.queries.getNodeById(fromId);
432
+ const toNode = this.queries.getNodeById(toId);
433
+ if (!fromNode || !toNode) {
434
+ return null;
435
+ }
436
+ // BFS to find shortest path
437
+ const visited = new Set();
438
+ const queue = [
439
+ { nodeId: fromId, path: [{ node: fromNode, edge: null }] },
440
+ ];
441
+ while (queue.length > 0) {
442
+ const { nodeId, path } = queue.shift();
443
+ if (nodeId === toId) {
444
+ return path;
445
+ }
446
+ if (visited.has(nodeId)) {
447
+ continue;
448
+ }
449
+ visited.add(nodeId);
450
+ // Get outgoing edges
451
+ const outgoingEdges = this.queries.getOutgoingEdges(nodeId, edgeKinds.length > 0 ? edgeKinds : undefined);
452
+ if (outgoingEdges.length === 0)
453
+ continue;
454
+ // Batch-fetch only the unvisited targets (was N+1 per BFS frontier).
455
+ const wantIds = outgoingEdges
456
+ .map((e) => e.target)
457
+ .filter((id) => !visited.has(id));
458
+ const nextNodes = wantIds.length > 0 ? this.queries.getNodesByIds(wantIds) : new Map();
459
+ for (const edge of outgoingEdges) {
460
+ if (!visited.has(edge.target)) {
461
+ const nextNode = nextNodes.get(edge.target);
462
+ if (nextNode) {
463
+ queue.push({
464
+ nodeId: edge.target,
465
+ path: [...path, { node: nextNode, edge }],
466
+ });
467
+ }
468
+ }
469
+ }
470
+ }
471
+ return null; // No path found
472
+ }
473
+ /**
474
+ * Get the containment hierarchy for a node (ancestors)
475
+ *
476
+ * @param nodeId - ID of the node
477
+ * @returns Array of ancestor nodes from immediate parent to root
478
+ */
479
+ getAncestors(nodeId) {
480
+ const ancestors = [];
481
+ const visited = new Set();
482
+ let currentId = nodeId;
483
+ while (true) {
484
+ if (visited.has(currentId)) {
485
+ break;
486
+ }
487
+ visited.add(currentId);
488
+ // Look for 'contains' edges pointing to this node
489
+ const containingEdges = this.queries.getIncomingEdges(currentId, ['contains']);
490
+ const firstEdge = containingEdges[0];
491
+ if (!firstEdge) {
492
+ break;
493
+ }
494
+ // Typically there should be at most one containing parent
495
+ const parentNode = this.queries.getNodeById(firstEdge.source);
496
+ if (parentNode) {
497
+ ancestors.push(parentNode);
498
+ currentId = parentNode.id;
499
+ }
500
+ else {
501
+ break;
502
+ }
503
+ }
504
+ return ancestors;
505
+ }
506
+ /**
507
+ * Get immediate children of a node
508
+ *
509
+ * @param nodeId - ID of the node
510
+ * @returns Array of child nodes
511
+ */
512
+ getChildren(nodeId) {
513
+ const containsEdges = this.queries.getOutgoingEdges(nodeId, ['contains']);
514
+ if (containsEdges.length === 0)
515
+ return [];
516
+ // Batch-fetch (was N+1).
517
+ const childNodes = this.queries.getNodesByIds(containsEdges.map((e) => e.target));
518
+ const children = [];
519
+ for (const edge of containsEdges) {
520
+ const childNode = childNodes.get(edge.target);
521
+ if (childNode)
522
+ children.push(childNode);
523
+ }
524
+ return children;
525
+ }
526
+ }
527
+ exports.GraphTraverser = GraphTraverser;
528
+ //# sourceMappingURL=traversal.js.map