@optave/codegraph 3.1.4 → 3.2.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 (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Stage: buildEdges
3
+ *
4
+ * Builds import, call, receiver, extends, and implements edges.
5
+ * Uses pre-loaded node lookup maps (N+1 optimization).
6
+ */
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { getNodeId } from '../../../../db/index.js';
10
+ import { loadNative } from '../../../../infrastructure/native.js';
11
+ import { computeConfidence } from '../../resolve.js';
12
+ import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
13
+ import { getResolved, isBarrelFile, resolveBarrelExport } from './resolve-imports.js';
14
+
15
+ // ── Node lookup setup ───────────────────────────────────────────────────
16
+
17
+ function makeGetNodeIdStmt(db) {
18
+ return {
19
+ get: (name, kind, file, line) => {
20
+ const id = getNodeId(db, name, kind, file, line);
21
+ return id != null ? { id } : undefined;
22
+ },
23
+ };
24
+ }
25
+
26
+ function setupNodeLookups(ctx, allNodes) {
27
+ ctx.nodesByName = new Map();
28
+ for (const node of allNodes) {
29
+ if (!ctx.nodesByName.has(node.name)) ctx.nodesByName.set(node.name, []);
30
+ ctx.nodesByName.get(node.name).push(node);
31
+ }
32
+ ctx.nodesByNameAndFile = new Map();
33
+ for (const node of allNodes) {
34
+ const key = `${node.name}|${node.file}`;
35
+ if (!ctx.nodesByNameAndFile.has(key)) ctx.nodesByNameAndFile.set(key, []);
36
+ ctx.nodesByNameAndFile.get(key).push(node);
37
+ }
38
+ }
39
+
40
+ // ── Import edges ────────────────────────────────────────────────────────
41
+
42
+ function buildImportEdges(ctx, getNodeIdStmt, allEdgeRows) {
43
+ const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
44
+
45
+ for (const [relPath, symbols] of fileSymbols) {
46
+ if (barrelOnlyFiles.has(relPath)) continue;
47
+ const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
48
+ if (!fileNodeRow) continue;
49
+ const fileNodeId = fileNodeRow.id;
50
+
51
+ for (const imp of symbols.imports) {
52
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
53
+ const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
54
+ if (!targetRow) continue;
55
+
56
+ const edgeKind = imp.reexport
57
+ ? 'reexports'
58
+ : imp.typeOnly
59
+ ? 'imports-type'
60
+ : imp.dynamicImport
61
+ ? 'dynamic-imports'
62
+ : 'imports';
63
+ allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
64
+
65
+ if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
66
+ buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, allEdgeRows);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ function buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, edgeRows) {
73
+ const resolvedSources = new Set();
74
+ for (const name of imp.names) {
75
+ const cleanName = name.replace(/^\*\s+as\s+/, '');
76
+ const actualSource = resolveBarrelExport(ctx, resolvedPath, cleanName);
77
+ if (actualSource && actualSource !== resolvedPath && !resolvedSources.has(actualSource)) {
78
+ resolvedSources.add(actualSource);
79
+ const actualRow = getNodeIdStmt.get(actualSource, 'file', actualSource, 0);
80
+ if (actualRow) {
81
+ const kind =
82
+ edgeKind === 'imports-type'
83
+ ? 'imports-type'
84
+ : edgeKind === 'dynamic-imports'
85
+ ? 'dynamic-imports'
86
+ : 'imports';
87
+ edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0]);
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ // ── Call edges (native engine) ──────────────────────────────────────────
94
+
95
+ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native) {
96
+ const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
97
+ const nativeFiles = [];
98
+
99
+ for (const [relPath, symbols] of fileSymbols) {
100
+ if (barrelOnlyFiles.has(relPath)) continue;
101
+ const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
102
+ if (!fileNodeRow) continue;
103
+
104
+ const importedNames = buildImportedNamesForNative(ctx, relPath, symbols, rootDir);
105
+ nativeFiles.push({
106
+ file: relPath,
107
+ fileNodeId: fileNodeRow.id,
108
+ definitions: symbols.definitions.map((d) => ({
109
+ name: d.name,
110
+ kind: d.kind,
111
+ line: d.line,
112
+ endLine: d.endLine ?? null,
113
+ })),
114
+ calls: symbols.calls,
115
+ importedNames,
116
+ classes: symbols.classes,
117
+ });
118
+ }
119
+
120
+ const nativeEdges = native.buildCallEdges(nativeFiles, allNodes, [...BUILTIN_RECEIVERS]);
121
+ for (const e of nativeEdges) {
122
+ allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
123
+ }
124
+ }
125
+
126
+ function buildImportedNamesForNative(ctx, relPath, symbols, rootDir) {
127
+ const importedNames = [];
128
+ for (const imp of symbols.imports) {
129
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
130
+ for (const name of imp.names) {
131
+ const cleanName = name.replace(/^\*\s+as\s+/, '');
132
+ let targetFile = resolvedPath;
133
+ if (isBarrelFile(ctx, resolvedPath)) {
134
+ const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
135
+ if (actual) targetFile = actual;
136
+ }
137
+ importedNames.push({ name: cleanName, file: targetFile });
138
+ }
139
+ }
140
+ return importedNames;
141
+ }
142
+
143
+ // ── Call edges (JS fallback) ────────────────────────────────────────────
144
+
145
+ function buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows) {
146
+ const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
147
+
148
+ for (const [relPath, symbols] of fileSymbols) {
149
+ if (barrelOnlyFiles.has(relPath)) continue;
150
+ const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
151
+ if (!fileNodeRow) continue;
152
+
153
+ const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
154
+ const seenCallEdges = new Set();
155
+
156
+ buildFileCallEdges(
157
+ ctx,
158
+ relPath,
159
+ symbols,
160
+ fileNodeRow,
161
+ importedNames,
162
+ seenCallEdges,
163
+ getNodeIdStmt,
164
+ allEdgeRows,
165
+ );
166
+ buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
167
+ }
168
+ }
169
+
170
+ function buildImportedNamesMap(ctx, relPath, symbols, rootDir) {
171
+ const importedNames = new Map();
172
+ for (const imp of symbols.imports) {
173
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
174
+ for (const name of imp.names) {
175
+ importedNames.set(name.replace(/^\*\s+as\s+/, ''), resolvedPath);
176
+ }
177
+ }
178
+ return importedNames;
179
+ }
180
+
181
+ function findCaller(call, definitions, relPath, getNodeIdStmt, fileNodeRow) {
182
+ let caller = null;
183
+ let callerSpan = Infinity;
184
+ for (const def of definitions) {
185
+ if (def.line <= call.line) {
186
+ const end = def.endLine || Infinity;
187
+ if (call.line <= end) {
188
+ const span = end - def.line;
189
+ if (span < callerSpan) {
190
+ const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
191
+ if (row) {
192
+ caller = row;
193
+ callerSpan = span;
194
+ }
195
+ }
196
+ } else if (!caller) {
197
+ const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
198
+ if (row) caller = row;
199
+ }
200
+ }
201
+ }
202
+ return caller || fileNodeRow;
203
+ }
204
+
205
+ function resolveCallTargets(ctx, call, relPath, importedNames) {
206
+ const importedFrom = importedNames.get(call.name);
207
+ let targets;
208
+
209
+ if (importedFrom) {
210
+ targets = ctx.nodesByNameAndFile.get(`${call.name}|${importedFrom}`) || [];
211
+ if (targets.length === 0 && isBarrelFile(ctx, importedFrom)) {
212
+ const actualSource = resolveBarrelExport(ctx, importedFrom, call.name);
213
+ if (actualSource) {
214
+ targets = ctx.nodesByNameAndFile.get(`${call.name}|${actualSource}`) || [];
215
+ }
216
+ }
217
+ }
218
+
219
+ if (!targets || targets.length === 0) {
220
+ targets = ctx.nodesByNameAndFile.get(`${call.name}|${relPath}`) || [];
221
+ if (targets.length === 0) {
222
+ targets = resolveByMethodOrGlobal(ctx, call, relPath);
223
+ }
224
+ }
225
+
226
+ if (targets.length > 1) {
227
+ targets.sort((a, b) => {
228
+ const confA = computeConfidence(relPath, a.file, importedFrom);
229
+ const confB = computeConfidence(relPath, b.file, importedFrom);
230
+ return confB - confA;
231
+ });
232
+ }
233
+
234
+ return { targets, importedFrom };
235
+ }
236
+
237
+ function resolveByMethodOrGlobal(ctx, call, relPath) {
238
+ const methodCandidates = (ctx.nodesByName.get(call.name) || []).filter(
239
+ (n) => n.name.endsWith(`.${call.name}`) && n.kind === 'method',
240
+ );
241
+ if (methodCandidates.length > 0) return methodCandidates;
242
+
243
+ if (
244
+ !call.receiver ||
245
+ call.receiver === 'this' ||
246
+ call.receiver === 'self' ||
247
+ call.receiver === 'super'
248
+ ) {
249
+ return (ctx.nodesByName.get(call.name) || []).filter(
250
+ (n) => computeConfidence(relPath, n.file, null) >= 0.5,
251
+ );
252
+ }
253
+ return [];
254
+ }
255
+
256
+ function buildFileCallEdges(
257
+ ctx,
258
+ relPath,
259
+ symbols,
260
+ fileNodeRow,
261
+ importedNames,
262
+ seenCallEdges,
263
+ getNodeIdStmt,
264
+ allEdgeRows,
265
+ ) {
266
+ for (const call of symbols.calls) {
267
+ if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
268
+
269
+ const caller = findCaller(call, symbols.definitions, relPath, getNodeIdStmt, fileNodeRow);
270
+ const isDynamic = call.dynamic ? 1 : 0;
271
+ const { targets, importedFrom } = resolveCallTargets(ctx, call, relPath, importedNames);
272
+
273
+ for (const t of targets) {
274
+ const edgeKey = `${caller.id}|${t.id}`;
275
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey)) {
276
+ seenCallEdges.add(edgeKey);
277
+ const confidence = computeConfidence(relPath, t.file, importedFrom);
278
+ allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic]);
279
+ }
280
+ }
281
+
282
+ // Receiver edge
283
+ if (
284
+ call.receiver &&
285
+ !BUILTIN_RECEIVERS.has(call.receiver) &&
286
+ call.receiver !== 'this' &&
287
+ call.receiver !== 'self' &&
288
+ call.receiver !== 'super'
289
+ ) {
290
+ buildReceiverEdge(ctx, call, caller, relPath, seenCallEdges, allEdgeRows);
291
+ }
292
+ }
293
+ }
294
+
295
+ function buildReceiverEdge(ctx, call, caller, relPath, seenCallEdges, allEdgeRows) {
296
+ const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
297
+ const samefile = ctx.nodesByNameAndFile.get(`${call.receiver}|${relPath}`) || [];
298
+ const candidates = samefile.length > 0 ? samefile : ctx.nodesByName.get(call.receiver) || [];
299
+ const receiverNodes = candidates.filter((n) => receiverKinds.has(n.kind));
300
+ if (receiverNodes.length > 0 && caller) {
301
+ const recvTarget = receiverNodes[0];
302
+ const recvKey = `recv|${caller.id}|${recvTarget.id}`;
303
+ if (!seenCallEdges.has(recvKey)) {
304
+ seenCallEdges.add(recvKey);
305
+ allEdgeRows.push([caller.id, recvTarget.id, 'receiver', 0.7, 0]);
306
+ }
307
+ }
308
+ }
309
+
310
+ // ── Class hierarchy edges ───────────────────────────────────────────────
311
+
312
+ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
313
+ for (const cls of symbols.classes) {
314
+ if (cls.extends) {
315
+ const sourceRow = (ctx.nodesByNameAndFile.get(`${cls.name}|${relPath}`) || []).find(
316
+ (n) => n.kind === 'class',
317
+ );
318
+ const targetRows = (ctx.nodesByName.get(cls.extends) || []).filter((n) => n.kind === 'class');
319
+ if (sourceRow) {
320
+ for (const t of targetRows) {
321
+ allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0]);
322
+ }
323
+ }
324
+ }
325
+
326
+ if (cls.implements) {
327
+ const sourceRow = (ctx.nodesByNameAndFile.get(`${cls.name}|${relPath}`) || []).find(
328
+ (n) => n.kind === 'class',
329
+ );
330
+ const targetRows = (ctx.nodesByName.get(cls.implements) || []).filter(
331
+ (n) => n.kind === 'interface' || n.kind === 'class',
332
+ );
333
+ if (sourceRow) {
334
+ for (const t of targetRows) {
335
+ allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0]);
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ // ── Main entry point ────────────────────────────────────────────────────
343
+
344
+ /**
345
+ * @param {import('../context.js').PipelineContext} ctx
346
+ */
347
+ export async function buildEdges(ctx) {
348
+ const { db, engineName } = ctx;
349
+
350
+ const getNodeIdStmt = makeGetNodeIdStmt(db);
351
+
352
+ const allNodes = db
353
+ .prepare(
354
+ `SELECT id, name, kind, file, line FROM nodes WHERE kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`,
355
+ )
356
+ .all();
357
+ setupNodeLookups(ctx, allNodes);
358
+
359
+ const t0 = performance.now();
360
+ const buildEdgesTx = db.transaction(() => {
361
+ const allEdgeRows = [];
362
+
363
+ buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
364
+
365
+ const native = engineName === 'native' ? loadNative() : null;
366
+ if (native?.buildCallEdges) {
367
+ buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native);
368
+ } else {
369
+ buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows);
370
+ }
371
+
372
+ batchInsertEdges(db, allEdgeRows);
373
+ });
374
+ buildEdgesTx();
375
+ ctx.timing.edgesMs = performance.now() - t0;
376
+ }
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import path from 'node:path';
7
7
  import { performance } from 'node:perf_hooks';
8
- import { normalizePath } from '../../constants.js';
9
- import { debug } from '../../logger.js';
8
+ import { debug } from '../../../../infrastructure/logger.js';
9
+ import { normalizePath } from '../../../../shared/constants.js';
10
10
  import { readFileSafe } from '../helpers.js';
11
11
 
12
12
  /**
@@ -88,7 +88,7 @@ export async function buildStructure(ctx) {
88
88
  relDirs.add(normalizePath(path.relative(rootDir, absDir)));
89
89
  }
90
90
  try {
91
- const { buildStructure: buildStructureFn } = await import('../../structure.js');
91
+ const { buildStructure: buildStructureFn } = await import('../../../../features/structure.js');
92
92
  const changedFilePaths = isFullBuild ? null : [...allSymbols.keys()];
93
93
  buildStructureFn(db, fileSymbols, rootDir, ctx.lineCountMap, relDirs, changedFilePaths);
94
94
  } catch (err) {
@@ -99,7 +99,7 @@ export async function buildStructure(ctx) {
99
99
  // Classify node roles
100
100
  const t1 = performance.now();
101
101
  try {
102
- const { classifyNodeRoles } = await import('../../structure.js');
102
+ const { classifyNodeRoles } = await import('../../../../features/structure.js');
103
103
  const roleSummary = classifyNodeRoles(db);
104
104
  debug(
105
105
  `Roles: ${Object.entries(roleSummary)
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
- import { normalizePath } from '../../constants.js';
9
- import { info } from '../../logger.js';
8
+ import { info } from '../../../../infrastructure/logger.js';
9
+ import { normalizePath } from '../../../../shared/constants.js';
10
10
  import { collectFiles as collectFilesUtil } from '../helpers.js';
11
11
 
12
12
  /**