@optave/codegraph 3.8.0 → 3.9.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 (310) hide show
  1. package/README.md +13 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +137 -86
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  28. package/dist/cli/commands/branch-compare.js +4 -0
  29. package/dist/cli/commands/branch-compare.js.map +1 -1
  30. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  31. package/dist/cli/commands/diff-impact.js +2 -1
  32. package/dist/cli/commands/diff-impact.js.map +1 -1
  33. package/dist/cli/commands/info.d.ts.map +1 -1
  34. package/dist/cli/commands/info.js +3 -2
  35. package/dist/cli/commands/info.js.map +1 -1
  36. package/dist/cli/commands/watch.d.ts.map +1 -1
  37. package/dist/cli/commands/watch.js +16 -2
  38. package/dist/cli/commands/watch.js.map +1 -1
  39. package/dist/db/connection.d.ts.map +1 -1
  40. package/dist/db/connection.js +29 -26
  41. package/dist/db/connection.js.map +1 -1
  42. package/dist/db/query-builder.d.ts.map +1 -1
  43. package/dist/db/query-builder.js +16 -5
  44. package/dist/db/query-builder.js.map +1 -1
  45. package/dist/db/repository/base.d.ts +16 -0
  46. package/dist/db/repository/base.d.ts.map +1 -1
  47. package/dist/db/repository/base.js +31 -0
  48. package/dist/db/repository/base.js.map +1 -1
  49. package/dist/db/repository/native-repository.d.ts +7 -1
  50. package/dist/db/repository/native-repository.d.ts.map +1 -1
  51. package/dist/db/repository/native-repository.js +100 -1
  52. package/dist/db/repository/native-repository.js.map +1 -1
  53. package/dist/db/repository/nodes.d.ts.map +1 -1
  54. package/dist/db/repository/nodes.js +8 -4
  55. package/dist/db/repository/nodes.js.map +1 -1
  56. package/dist/db/repository/sqlite-repository.d.ts +4 -0
  57. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  58. package/dist/db/repository/sqlite-repository.js +51 -0
  59. package/dist/db/repository/sqlite-repository.js.map +1 -1
  60. package/dist/domain/analysis/brief.d.ts.map +1 -1
  61. package/dist/domain/analysis/brief.js +13 -17
  62. package/dist/domain/analysis/brief.js.map +1 -1
  63. package/dist/domain/analysis/context.d.ts.map +1 -1
  64. package/dist/domain/analysis/context.js +14 -11
  65. package/dist/domain/analysis/context.js.map +1 -1
  66. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  67. package/dist/domain/analysis/dependencies.js +64 -59
  68. package/dist/domain/analysis/dependencies.js.map +1 -1
  69. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  70. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  71. package/dist/domain/analysis/fn-impact.js +33 -31
  72. package/dist/domain/analysis/fn-impact.js.map +1 -1
  73. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  74. package/dist/domain/analysis/implementations.js +11 -19
  75. package/dist/domain/analysis/implementations.js.map +1 -1
  76. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  77. package/dist/domain/analysis/module-map.js +55 -76
  78. package/dist/domain/analysis/module-map.js.map +1 -1
  79. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  80. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  81. package/dist/domain/analysis/query-helpers.js +15 -1
  82. package/dist/domain/analysis/query-helpers.js.map +1 -1
  83. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  84. package/dist/domain/graph/builder/pipeline.js +352 -107
  85. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  86. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/build-edges.js +49 -18
  88. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  90. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  91. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  92. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  93. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  94. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  95. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  96. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  97. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  98. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  99. package/dist/domain/graph/cycles.d.ts +6 -0
  100. package/dist/domain/graph/cycles.d.ts.map +1 -1
  101. package/dist/domain/graph/cycles.js +114 -22
  102. package/dist/domain/graph/cycles.js.map +1 -1
  103. package/dist/domain/graph/resolve.js +1 -1
  104. package/dist/domain/graph/resolve.js.map +1 -1
  105. package/dist/domain/graph/watcher.d.ts +2 -0
  106. package/dist/domain/graph/watcher.d.ts.map +1 -1
  107. package/dist/domain/graph/watcher.js +170 -75
  108. package/dist/domain/graph/watcher.js.map +1 -1
  109. package/dist/domain/parser.d.ts +3 -4
  110. package/dist/domain/parser.d.ts.map +1 -1
  111. package/dist/domain/parser.js +141 -89
  112. package/dist/domain/parser.js.map +1 -1
  113. package/dist/domain/search/generator.js +1 -1
  114. package/dist/domain/search/generator.js.map +1 -1
  115. package/dist/domain/search/models.d.ts +4 -3
  116. package/dist/domain/search/models.d.ts.map +1 -1
  117. package/dist/domain/search/models.js +23 -8
  118. package/dist/domain/search/models.js.map +1 -1
  119. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  120. package/dist/domain/search/search/hybrid.js +29 -18
  121. package/dist/domain/search/search/hybrid.js.map +1 -1
  122. package/dist/extractors/go.js +36 -33
  123. package/dist/extractors/go.js.map +1 -1
  124. package/dist/extractors/helpers.d.ts.map +1 -1
  125. package/dist/extractors/helpers.js +40 -29
  126. package/dist/extractors/helpers.js.map +1 -1
  127. package/dist/extractors/java.js +58 -46
  128. package/dist/extractors/java.js.map +1 -1
  129. package/dist/extractors/javascript.js +65 -54
  130. package/dist/extractors/javascript.js.map +1 -1
  131. package/dist/extractors/kotlin.js +84 -78
  132. package/dist/extractors/kotlin.js.map +1 -1
  133. package/dist/extractors/python.js +29 -24
  134. package/dist/extractors/python.js.map +1 -1
  135. package/dist/extractors/rust.js +41 -32
  136. package/dist/extractors/rust.js.map +1 -1
  137. package/dist/extractors/solidity.js +58 -67
  138. package/dist/extractors/solidity.js.map +1 -1
  139. package/dist/extractors/swift.js +83 -81
  140. package/dist/extractors/swift.js.map +1 -1
  141. package/dist/extractors/zig.js +58 -60
  142. package/dist/extractors/zig.js.map +1 -1
  143. package/dist/features/ast.d.ts +16 -14
  144. package/dist/features/ast.d.ts.map +1 -1
  145. package/dist/features/ast.js +83 -81
  146. package/dist/features/ast.js.map +1 -1
  147. package/dist/features/audit.d.ts.map +1 -1
  148. package/dist/features/audit.js +8 -6
  149. package/dist/features/audit.js.map +1 -1
  150. package/dist/features/branch-compare.d.ts.map +1 -1
  151. package/dist/features/branch-compare.js +69 -72
  152. package/dist/features/branch-compare.js.map +1 -1
  153. package/dist/features/communities.d.ts.map +1 -1
  154. package/dist/features/communities.js +19 -7
  155. package/dist/features/communities.js.map +1 -1
  156. package/dist/features/complexity.d.ts.map +1 -1
  157. package/dist/features/complexity.js +120 -125
  158. package/dist/features/complexity.js.map +1 -1
  159. package/dist/features/dataflow.d.ts.map +1 -1
  160. package/dist/features/dataflow.js +136 -137
  161. package/dist/features/dataflow.js.map +1 -1
  162. package/dist/features/flow.d.ts.map +1 -1
  163. package/dist/features/flow.js +84 -79
  164. package/dist/features/flow.js.map +1 -1
  165. package/dist/features/structure-query.d.ts.map +1 -1
  166. package/dist/features/structure-query.js +69 -65
  167. package/dist/features/structure-query.js.map +1 -1
  168. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  169. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  170. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  171. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  172. package/dist/graph/algorithms/leiden/partition.js +288 -266
  173. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  174. package/dist/graph/model.d.ts.map +1 -1
  175. package/dist/graph/model.js +5 -1
  176. package/dist/graph/model.js.map +1 -1
  177. package/dist/infrastructure/config.d.ts.map +1 -1
  178. package/dist/infrastructure/config.js +6 -4
  179. package/dist/infrastructure/config.js.map +1 -1
  180. package/dist/infrastructure/suppress.d.ts +25 -0
  181. package/dist/infrastructure/suppress.d.ts.map +1 -0
  182. package/dist/infrastructure/suppress.js +43 -0
  183. package/dist/infrastructure/suppress.js.map +1 -0
  184. package/dist/mcp/server.d.ts.map +1 -1
  185. package/dist/mcp/server.js +29 -24
  186. package/dist/mcp/server.js.map +1 -1
  187. package/dist/presentation/dataflow.d.ts.map +1 -1
  188. package/dist/presentation/dataflow.js +47 -38
  189. package/dist/presentation/dataflow.js.map +1 -1
  190. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  191. package/dist/presentation/diff-impact-mermaid.js +60 -51
  192. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  193. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/exports.js +20 -14
  195. package/dist/presentation/queries-cli/exports.js.map +1 -1
  196. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  197. package/dist/presentation/queries-cli/impact.js +15 -13
  198. package/dist/presentation/queries-cli/impact.js.map +1 -1
  199. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  200. package/dist/presentation/queries-cli/inspect.js +101 -79
  201. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  202. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  203. package/dist/presentation/queries-cli/overview.js +25 -16
  204. package/dist/presentation/queries-cli/overview.js.map +1 -1
  205. package/dist/presentation/queries-cli/path.js +26 -20
  206. package/dist/presentation/queries-cli/path.js.map +1 -1
  207. package/dist/presentation/result-formatter.d.ts +10 -0
  208. package/dist/presentation/result-formatter.d.ts.map +1 -1
  209. package/dist/presentation/result-formatter.js +16 -1
  210. package/dist/presentation/result-formatter.js.map +1 -1
  211. package/dist/presentation/viewer.d.ts.map +1 -1
  212. package/dist/presentation/viewer.js +18 -12
  213. package/dist/presentation/viewer.js.map +1 -1
  214. package/dist/shared/errors.d.ts +5 -0
  215. package/dist/shared/errors.d.ts.map +1 -1
  216. package/dist/shared/errors.js +5 -0
  217. package/dist/shared/errors.js.map +1 -1
  218. package/dist/shared/hierarchy.d.ts +8 -2
  219. package/dist/shared/hierarchy.d.ts.map +1 -1
  220. package/dist/shared/hierarchy.js +42 -1
  221. package/dist/shared/hierarchy.js.map +1 -1
  222. package/dist/shared/normalize.d.ts +6 -1
  223. package/dist/shared/normalize.d.ts.map +1 -1
  224. package/dist/shared/normalize.js +20 -12
  225. package/dist/shared/normalize.js.map +1 -1
  226. package/dist/shared/paginate.d.ts +0 -9
  227. package/dist/shared/paginate.d.ts.map +1 -1
  228. package/dist/shared/paginate.js +0 -15
  229. package/dist/shared/paginate.js.map +1 -1
  230. package/dist/types.d.ts +12 -5
  231. package/dist/types.d.ts.map +1 -1
  232. package/grammars/tree-sitter-erlang.wasm +0 -0
  233. package/grammars/tree-sitter-gleam.wasm +0 -0
  234. package/package.json +9 -9
  235. package/src/ast-analysis/engine.ts +176 -104
  236. package/src/ast-analysis/metrics.ts +33 -11
  237. package/src/ast-analysis/shared.ts +33 -24
  238. package/src/ast-analysis/visitor-utils.ts +52 -32
  239. package/src/ast-analysis/visitor.ts +132 -71
  240. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  241. package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
  242. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  243. package/src/cli/commands/branch-compare.ts +4 -0
  244. package/src/cli/commands/diff-impact.ts +2 -1
  245. package/src/cli/commands/info.ts +3 -2
  246. package/src/cli/commands/watch.ts +16 -2
  247. package/src/db/connection.ts +29 -28
  248. package/src/db/query-builder.ts +15 -3
  249. package/src/db/repository/base.ts +34 -0
  250. package/src/db/repository/native-repository.ts +104 -1
  251. package/src/db/repository/nodes.ts +13 -8
  252. package/src/db/repository/sqlite-repository.ts +55 -0
  253. package/src/domain/analysis/brief.ts +15 -25
  254. package/src/domain/analysis/context.ts +17 -10
  255. package/src/domain/analysis/dependencies.ts +77 -81
  256. package/src/domain/analysis/fn-impact.ts +36 -43
  257. package/src/domain/analysis/implementations.ts +11 -17
  258. package/src/domain/analysis/module-map.ts +58 -92
  259. package/src/domain/analysis/query-helpers.ts +18 -1
  260. package/src/domain/graph/builder/pipeline.ts +409 -99
  261. package/src/domain/graph/builder/stages/build-edges.ts +45 -19
  262. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  263. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  264. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  265. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  266. package/src/domain/graph/cycles.ts +110 -23
  267. package/src/domain/graph/resolve.ts +1 -1
  268. package/src/domain/graph/watcher.ts +202 -96
  269. package/src/domain/parser.ts +143 -89
  270. package/src/domain/search/generator.ts +1 -1
  271. package/src/domain/search/models.ts +26 -7
  272. package/src/domain/search/search/hybrid.ts +69 -51
  273. package/src/extractors/go.ts +43 -33
  274. package/src/extractors/helpers.ts +37 -23
  275. package/src/extractors/java.ts +66 -47
  276. package/src/extractors/javascript.ts +66 -54
  277. package/src/extractors/kotlin.ts +84 -77
  278. package/src/extractors/python.ts +31 -25
  279. package/src/extractors/rust.ts +37 -29
  280. package/src/extractors/solidity.ts +57 -61
  281. package/src/extractors/swift.ts +81 -80
  282. package/src/extractors/zig.ts +58 -61
  283. package/src/features/ast.ts +130 -110
  284. package/src/features/audit.ts +8 -6
  285. package/src/features/branch-compare.ts +105 -79
  286. package/src/features/communities.ts +25 -10
  287. package/src/features/complexity.ts +171 -134
  288. package/src/features/dataflow.ts +165 -175
  289. package/src/features/flow.ts +129 -92
  290. package/src/features/structure-query.ts +79 -64
  291. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  292. package/src/graph/algorithms/leiden/partition.ts +359 -294
  293. package/src/graph/model.ts +6 -1
  294. package/src/infrastructure/config.ts +6 -4
  295. package/src/infrastructure/suppress.ts +47 -0
  296. package/src/mcp/server.ts +53 -37
  297. package/src/presentation/dataflow.ts +50 -44
  298. package/src/presentation/diff-impact-mermaid.ts +104 -62
  299. package/src/presentation/queries-cli/exports.ts +21 -13
  300. package/src/presentation/queries-cli/impact.ts +15 -13
  301. package/src/presentation/queries-cli/inspect.ts +100 -81
  302. package/src/presentation/queries-cli/overview.ts +26 -16
  303. package/src/presentation/queries-cli/path.ts +33 -25
  304. package/src/presentation/result-formatter.ts +19 -1
  305. package/src/presentation/viewer.ts +42 -14
  306. package/src/shared/errors.ts +6 -0
  307. package/src/shared/hierarchy.ts +50 -2
  308. package/src/shared/normalize.ts +31 -12
  309. package/src/shared/paginate.ts +0 -17
  310. package/src/types.ts +26 -5
@@ -197,8 +197,13 @@ function buildImportEdgesNative(
197
197
  };
198
198
  const fileNodeRowCache = new Map<string, { id: number }>();
199
199
 
200
- // 2. Pre-resolve all imports and collect supplemental resolved entries
201
- const supplementalResolved: Array<{ key: string; resolvedPath: string }> = [];
200
+ // 2. Pre-resolve all imports and build resolved imports array.
201
+ // Keys use forward-slash-normalized rootDir + "/" + relPath to match the Rust
202
+ // lookup format (format!("{}/{}", root_dir.replace('\\', "/"), file)).
203
+ // On Windows, rootDir has backslashes but Rust normalizes them — the JS side
204
+ // must do the same or every resolve key lookup misses (#750).
205
+ const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
206
+ const fwdRootDir = rootDir.replace(/\\/g, '/');
202
207
 
203
208
  for (const [relPath, symbols] of fileSymbols) {
204
209
  const fileNodeRow = addFileNodeId(relPath);
@@ -218,11 +223,8 @@ function buildImportEdgesNative(
218
223
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
219
224
  addFileNodeId(resolvedPath);
220
225
 
221
- // Check if this resolution is in batchResolved; if not, add supplemental
222
- const resolveKey = `${path.join(rootDir, relPath)}|${imp.source}`;
223
- if (!ctx.batchResolved?.has(resolveKey)) {
224
- supplementalResolved.push({ key: resolveKey, resolvedPath });
225
- }
226
+ // Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
227
+ resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
226
228
 
227
229
  importInfos.push({
228
230
  source: imp.source,
@@ -243,17 +245,6 @@ function buildImportEdgesNative(
243
245
  });
244
246
  }
245
247
 
246
- // 3. Flatten batchResolved + supplemental into resolved imports array
247
- const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
248
- if (ctx.batchResolved) {
249
- for (const [key, resolvedPath] of ctx.batchResolved) {
250
- resolvedImports.push({ key, resolvedPath });
251
- }
252
- }
253
- for (const entry of supplementalResolved) {
254
- resolvedImports.push(entry);
255
- }
256
-
257
248
  // 4. Flatten reexportMap
258
249
  const fileReexports: Array<{
259
250
  file: string;
@@ -363,7 +354,10 @@ function buildImportedNamesForNative(
363
354
  rootDir: string,
364
355
  ): Array<{ name: string; file: string }> {
365
356
  const importedNames: Array<{ name: string; file: string }> = [];
366
- for (const imp of symbols.imports) {
357
+ // Process dynamic imports first (lower priority), then static imports
358
+ // (higher priority). Rust HashMap::collect keeps the last entry per key,
359
+ // so static imports win when both contribute the same name.
360
+ const addImports = (imp: (typeof symbols.imports)[number]) => {
367
361
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
368
362
  for (const name of imp.names) {
369
363
  const cleanName = name.replace(/^\*\s+as\s+/, '');
@@ -374,6 +368,12 @@ function buildImportedNamesForNative(
374
368
  }
375
369
  importedNames.push({ name: cleanName, file: targetFile });
376
370
  }
371
+ };
372
+ for (const imp of symbols.imports) {
373
+ if (imp.dynamicImport) addImports(imp);
374
+ }
375
+ for (const imp of symbols.imports) {
376
+ if (!imp.dynamicImport) addImports(imp);
377
377
  }
378
378
  return importedNames;
379
379
  }
@@ -418,12 +418,25 @@ function buildImportedNamesMap(
418
418
  rootDir: string,
419
419
  ): Map<string, string> {
420
420
  const importedNames = new Map<string, string>();
421
+ // Process dynamic imports first (lower priority), then static imports
422
+ // (higher priority). Static imports represent direct bindings while dynamic
423
+ // imports often use aliased destructuring (`{ foo: bar } = await import(…)`).
424
+ // When both contribute the same name, the static binding is authoritative.
421
425
  for (const imp of symbols.imports) {
426
+ if (!imp.dynamicImport) continue;
422
427
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
423
428
  for (const name of imp.names) {
424
429
  importedNames.set(name.replace(/^\*\s+as\s+/, ''), resolvedPath);
425
430
  }
426
431
  }
432
+ for (const imp of symbols.imports) {
433
+ if (imp.dynamicImport) continue;
434
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
435
+ for (const name of imp.names) {
436
+ const cleanName = name.replace(/^\*\s+as\s+/, '');
437
+ importedNames.set(cleanName, resolvedPath);
438
+ }
439
+ }
427
440
  return importedNames;
428
441
  }
429
442
 
@@ -748,7 +761,20 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
748
761
  const useNativeImportEdges =
749
762
  native?.buildImportEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
750
763
  if (useNativeImportEdges) {
764
+ const beforeLen = allEdgeRows.length;
751
765
  buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native!);
766
+ // Fallback: if native produced 0 import edges but there are imports to
767
+ // process, the native binary may have a key-format mismatch (e.g. Windows
768
+ // path separators — #750). Retry with the JS implementation.
769
+ // NOTE: This also fires for codebases where every import targets an
770
+ // external package (npm deps) that the resolver intentionally skips.
771
+ // In that case the JS path resolves zero edges too, so the only cost
772
+ // is the redundant JS traversal — no correctness impact.
773
+ const hasImports = [...ctx.fileSymbols.values()].some((s) => s.imports.length > 0);
774
+ if (allEdgeRows.length === beforeLen && hasImports) {
775
+ debug('Native buildImportEdges produced 0 edges — falling back to JS');
776
+ buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
777
+ }
752
778
  } else {
753
779
  buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
754
780
  }
@@ -349,7 +349,7 @@ function findReverseDependencies(
349
349
  const changedArray = [...changedRelPaths];
350
350
  const nativeResults = nativeDb.findReverseDependencies(changedArray);
351
351
  for (const dep of nativeResults) {
352
- const absPath = path.join(rootDir, dep);
352
+ const absPath = path.isAbsolute(dep) ? dep : path.join(rootDir, dep);
353
353
  if (fs.existsSync(absPath)) {
354
354
  reverseDeps.add(dep);
355
355
  }
@@ -366,7 +366,7 @@ function findReverseDependencies(
366
366
  for (const relPath of changedRelPaths) {
367
367
  for (const row of findReverseDepsStmt.all(relPath) as Array<{ file: string }>) {
368
368
  if (!changedRelPaths.has(row.file) && !reverseDeps.has(row.file)) {
369
- const absPath = path.join(rootDir, row.file);
369
+ const absPath = path.isAbsolute(row.file) ? row.file : path.join(rootDir, row.file);
370
370
  if (fs.existsSync(absPath)) {
371
371
  reverseDeps.add(row.file);
372
372
  }
@@ -86,7 +86,7 @@ function persistBuildMetadata(
86
86
  ctx.nativeDb!.setBuildMeta(
87
87
  Object.entries({
88
88
  engine: ctx.engineName,
89
- engine_version: ctx.engineVersion || '',
89
+ engine_version: CODEGRAPH_VERSION,
90
90
  codegraph_version: CODEGRAPH_VERSION,
91
91
  schema_version: String(ctx.schemaVersion),
92
92
  built_at: buildNow.toISOString(),
@@ -97,7 +97,7 @@ function persistBuildMetadata(
97
97
  } else {
98
98
  setBuildMeta(ctx.db, {
99
99
  engine: ctx.engineName,
100
- engine_version: ctx.engineVersion || '',
100
+ engine_version: CODEGRAPH_VERSION,
101
101
  codegraph_version: CODEGRAPH_VERSION,
102
102
  schema_version: String(ctx.schemaVersion),
103
103
  built_at: buildNow.toISOString(),
@@ -11,6 +11,8 @@
11
11
  import path from 'node:path';
12
12
  import { performance } from 'node:perf_hooks';
13
13
  import { bulkNodeIdsByFile } from '../../../../db/index.js';
14
+ import { debug } from '../../../../infrastructure/logger.js';
15
+ import { toErrorMessage } from '../../../../shared/errors.js';
14
16
  import type {
15
17
  BetterSqlite3Database,
16
18
  ExtractorOutput,
@@ -36,35 +38,31 @@ interface PrecomputedFileData {
36
38
  _reverseDepOnly?: boolean;
37
39
  }
38
40
 
39
- // ── Native fast-path ─────────────────────────────────────────────────
41
+ // ── Native fast-path helpers ─────────────────────────────────────────
40
42
 
41
- function tryNativeInsert(ctx: PipelineContext): boolean {
42
- // Use NativeDatabase persistent connection (Phase 6.15+).
43
- // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
44
- if (!ctx.nativeDb?.bulkInsertNodes) return false;
45
-
46
- const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
47
-
48
- // Marshal allSymbols → InsertNodesBatch[]
49
- const batches: Array<{
50
- file: string;
51
- definitions: Array<{
43
+ /** Shape of a marshaled batch for native bulk insert. */
44
+ interface InsertNodesBatch {
45
+ file: string;
46
+ definitions: Array<{
47
+ name: string;
48
+ kind: string;
49
+ line: number;
50
+ endLine?: number;
51
+ visibility?: string;
52
+ children: Array<{
52
53
  name: string;
53
54
  kind: string;
54
55
  line: number;
55
56
  endLine?: number;
56
57
  visibility?: string;
57
- children: Array<{
58
- name: string;
59
- kind: string;
60
- line: number;
61
- endLine?: number;
62
- visibility?: string;
63
- }>;
64
58
  }>;
65
- exports: Array<{ name: string; kind: string; line: number }>;
66
- }> = [];
59
+ }>;
60
+ exports: Array<{ name: string; kind: string; line: number }>;
61
+ }
67
62
 
63
+ /** Marshal allSymbols into the batch format expected by native bulkInsertNodes. */
64
+ function marshalSymbolBatches(allSymbols: Map<string, ExtractorOutput>): InsertNodesBatch[] {
65
+ const batches: InsertNodesBatch[] = [];
68
66
  for (const [relPath, symbols] of allSymbols) {
69
67
  batches.push({
70
68
  file: relPath,
@@ -89,14 +87,18 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
89
87
  })),
90
88
  });
91
89
  }
90
+ return batches;
91
+ }
92
92
 
93
- // Build file hash entries
94
- const precomputedData = new Map<string, PrecomputedFileData>();
95
- for (const item of filesToParse) {
96
- if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
97
- }
98
-
93
+ /** Build file hash entries from parsed symbols and precomputed/metadata sources. */
94
+ function buildFileHashes(
95
+ allSymbols: Map<string, ExtractorOutput>,
96
+ precomputedData: Map<string, PrecomputedFileData>,
97
+ metadataUpdates: MetadataUpdate[],
98
+ rootDir: string,
99
+ ): Array<{ file: string; hash: string; mtime: number; size: number }> {
99
100
  const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
101
+
100
102
  for (const [relPath] of allSymbols) {
101
103
  const precomputed = precomputedData.get(relPath);
102
104
  if (precomputed?._reverseDepOnly) {
@@ -119,7 +121,8 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
119
121
  let code: string | null;
120
122
  try {
121
123
  code = readFileSafe(absPath);
122
- } catch {
124
+ } catch (e) {
125
+ debug(`buildFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
123
126
  code = null;
124
127
  }
125
128
  if (code !== null) {
@@ -138,6 +141,24 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
138
141
  fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
139
142
  }
140
143
 
144
+ return fileHashes;
145
+ }
146
+
147
+ // ── Native fast-path ─────────────────────────────────────────────────
148
+
149
+ function tryNativeInsert(ctx: PipelineContext): boolean {
150
+ if (!ctx.nativeDb?.bulkInsertNodes) return false;
151
+
152
+ const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
153
+
154
+ const batches = marshalSymbolBatches(allSymbols);
155
+
156
+ const precomputedData = new Map<string, PrecomputedFileData>();
157
+ for (const item of filesToParse) {
158
+ if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
159
+ }
160
+ const fileHashes = buildFileHashes(allSymbols, precomputedData, metadataUpdates, rootDir);
161
+
141
162
  // WAL guard: same suspendJsDb/resumeJsDb pattern used by feature modules
142
163
  // (ast, cfg, complexity, dataflow). Checkpoint JS side before native write,
143
164
  // then checkpoint native side after, so neither library reads WAL frames
@@ -151,8 +172,10 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
151
172
  } finally {
152
173
  try {
153
174
  ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
154
- } catch {
155
- /* ignore — nativeDb may already be closed */
175
+ } catch (e) {
176
+ debug(
177
+ `tryNativeInsert: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
178
+ );
156
179
  }
157
180
  }
158
181
  return result;
@@ -324,7 +347,8 @@ function updateFileHashes(
324
347
  let code: string | null;
325
348
  try {
326
349
  code = readFileSafe(absPath);
327
- } catch {
350
+ } catch (e) {
351
+ debug(`updateFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
328
352
  code = null;
329
353
  }
330
354
  if (code !== null) {
@@ -364,8 +388,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
364
388
  // Removed-file hash cleanup is handled inside the native call
365
389
  return;
366
390
  }
367
- } catch {
368
- // Native insert failed fall through to JS implementation
391
+ } catch (e) {
392
+ debug(`insertNodes: native insert failed, falling back to JS: ${toErrorMessage(e)}`);
369
393
  }
370
394
  }
371
395
 
@@ -380,7 +404,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
380
404
  upsertHash = ctx.db.prepare(
381
405
  'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
382
406
  );
383
- } catch {
407
+ } catch (e) {
408
+ debug(`insertNodes: file_hashes prepare failed (table may not exist): ${toErrorMessage(e)}`);
384
409
  upsertHash = null;
385
410
  }
386
411
 
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { performance } from 'node:perf_hooks';
3
3
  import { debug } from '../../../../infrastructure/logger.js';
4
+ import { normalizePath } from '../../../../shared/constants.js';
4
5
  import type { Import } from '../../../../types.js';
5
6
  import { parseFilesAuto } from '../../../parser.js';
6
7
  import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
@@ -12,20 +13,10 @@ interface ReexportEntry {
12
13
  wildcardReexport: boolean;
13
14
  }
14
15
 
15
- export async function resolveImports(ctx: PipelineContext): Promise<void> {
16
- const { db, fileSymbols, rootDir, aliases, allFiles, isFullBuild, engineOpts } = ctx;
17
- const t0 = performance.now();
18
- const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
19
- for (const [relPath, symbols] of fileSymbols) {
20
- const absFile = path.join(rootDir, relPath);
21
- for (const imp of symbols.imports) {
22
- batchInputs.push({ fromFile: absFile, importSource: imp.source });
23
- }
24
- }
25
- ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
26
- ctx.timing.resolveMs = performance.now() - t0;
27
-
16
+ /** Collect reexport entries from fileSymbols into the reexportMap. */
17
+ function buildReexportMap(ctx: PipelineContext): void {
28
18
  ctx.reexportMap = new Map<string, ReexportEntry[]>();
19
+ const { fileSymbols, rootDir } = ctx;
29
20
  for (const [relPath, symbols] of fileSymbols) {
30
21
  const reexports = symbols.imports.filter((imp) => imp.reexport);
31
22
  if (reexports.length > 0) {
@@ -39,110 +30,141 @@ export async function resolveImports(ctx: PipelineContext): Promise<void> {
39
30
  );
40
31
  }
41
32
  }
33
+ }
42
34
 
43
- ctx.barrelOnlyFiles = new Set<string>();
44
- if (!isFullBuild) {
45
- // Collect the set of changed file paths to scope barrel re-parsing.
46
- const changedRelPaths = new Set<string>(fileSymbols.keys());
47
-
48
- // For small incremental builds (≤5 files), only re-parse barrel files
49
- // that are related to the changed files — either re-exporting from them
50
- // or imported by them. For larger changes, re-parse all barrels.
51
- let barrelCandidates: Array<{ file: string }>;
52
- if (changedRelPaths.size <= 5) {
53
- // All known barrel files (has at least one reexport edge)
54
- const allBarrelFiles = new Set(
55
- (
56
- db
57
- .prepare(
58
- `SELECT DISTINCT n1.file FROM edges e
59
- JOIN nodes n1 ON e.source_id = n1.id
60
- WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
61
- )
62
- .all() as Array<{ file: string }>
63
- ).map((r) => r.file),
64
- );
35
+ /**
36
+ * Find barrel files related to changed files for scoped re-parsing.
37
+ * For small incremental builds (<=5 files), only barrels that re-export from
38
+ * or are imported by the changed files. For larger changes, all barrels.
39
+ */
40
+ function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
41
+ const { db, fileSymbols, rootDir, aliases } = ctx;
42
+ const changedRelPaths = new Set<string>(fileSymbols.keys());
65
43
 
66
- const barrels = new Set<string>();
67
-
68
- // Find barrels imported by changed files using parsed import data
69
- // (can't query DB edges — they were purged for the changed files).
70
- for (const relPath of changedRelPaths) {
71
- const symbols = fileSymbols.get(relPath);
72
- if (!symbols) continue;
73
- for (const imp of symbols.imports) {
74
- const resolved = ctx.batchResolved?.get(`${path.join(rootDir, relPath)}|${imp.source}`);
75
- const target =
76
- resolved ??
77
- resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
78
- if (allBarrelFiles.has(target)) barrels.add(target);
79
- }
80
- }
44
+ const SMALL_CHANGE_THRESHOLD = 5;
45
+ if (changedRelPaths.size <= SMALL_CHANGE_THRESHOLD) {
46
+ const allBarrelFiles = new Set(
47
+ (
48
+ db
49
+ .prepare(
50
+ `SELECT DISTINCT n1.file FROM edges e
51
+ JOIN nodes n1 ON e.source_id = n1.id
52
+ WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
53
+ )
54
+ .all() as Array<{ file: string }>
55
+ ).map((r) => r.file),
56
+ );
81
57
 
82
- // Also find barrels that re-export from the changed files
83
- const reexportSourceStmt = db.prepare(
84
- `SELECT DISTINCT n1.file FROM edges e
85
- JOIN nodes n1 ON e.source_id = n1.id
86
- JOIN nodes n2 ON e.target_id = n2.id
87
- WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
88
- );
89
- for (const relPath of changedRelPaths) {
90
- for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
91
- barrels.add(row.file);
92
- }
58
+ const barrels = new Set<string>();
59
+
60
+ // Find barrels imported by changed files using parsed import data
61
+ // (can't query DB edges -- they were purged for the changed files).
62
+ for (const relPath of changedRelPaths) {
63
+ const symbols = fileSymbols.get(relPath);
64
+ if (!symbols) continue;
65
+ for (const imp of symbols.imports) {
66
+ const resolved = ctx.batchResolved?.get(
67
+ `${normalizePath(path.join(rootDir, relPath))}|${imp.source}`,
68
+ );
69
+ const target =
70
+ resolved ?? resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
71
+ if (allBarrelFiles.has(target)) barrels.add(target);
93
72
  }
94
- barrelCandidates = [...barrels].map((file) => ({ file }));
95
- } else {
96
- barrelCandidates = db
97
- .prepare(
98
- `SELECT DISTINCT n1.file FROM edges e
99
- JOIN nodes n1 ON e.source_id = n1.id
100
- WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
101
- )
102
- .all() as Array<{ file: string }>;
103
73
  }
104
74
 
105
- // Batch-parse all barrel candidates at once instead of one-by-one
106
- const barrelPaths: string[] = [];
107
- for (const { file: relPath } of barrelCandidates) {
108
- if (!fileSymbols.has(relPath)) {
109
- barrelPaths.push(path.join(rootDir, relPath));
75
+ // Also find barrels that re-export from the changed files
76
+ const reexportSourceStmt = db.prepare(
77
+ `SELECT DISTINCT n1.file FROM edges e
78
+ JOIN nodes n1 ON e.source_id = n1.id
79
+ JOIN nodes n2 ON e.target_id = n2.id
80
+ WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
81
+ );
82
+ for (const relPath of changedRelPaths) {
83
+ for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
84
+ barrels.add(row.file);
110
85
  }
111
86
  }
87
+ return [...barrels].map((file) => ({ file }));
88
+ }
112
89
 
113
- if (barrelPaths.length > 0) {
114
- const deleteOutgoingEdges = db.prepare(
115
- 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
116
- );
90
+ return db
91
+ .prepare(
92
+ `SELECT DISTINCT n1.file FROM edges e
93
+ JOIN nodes n1 ON e.source_id = n1.id
94
+ WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
95
+ )
96
+ .all() as Array<{ file: string }>;
97
+ }
117
98
 
118
- try {
119
- const barrelSymbols = await parseFilesAuto(barrelPaths, rootDir, engineOpts);
120
- for (const [relPath, fileSym] of barrelSymbols) {
121
- deleteOutgoingEdges.run(relPath);
122
- fileSymbols.set(relPath, fileSym);
123
- ctx.barrelOnlyFiles.add(relPath);
124
- const reexports = fileSym.imports.filter((imp: Import) => imp.reexport);
125
- if (reexports.length > 0) {
126
- ctx.reexportMap.set(
127
- relPath,
128
- reexports.map((imp: Import) => ({
129
- source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
130
- names: imp.names,
131
- wildcardReexport: imp.wildcardReexport || false,
132
- })),
133
- );
134
- }
135
- }
136
- } catch (e: unknown) {
137
- debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
99
+ /** Re-parse barrel files and update fileSymbols/reexportMap with fresh data. */
100
+ async function reparseBarrelFiles(
101
+ ctx: PipelineContext,
102
+ barrelCandidates: Array<{ file: string }>,
103
+ ): Promise<void> {
104
+ const { db, fileSymbols, rootDir, engineOpts } = ctx;
105
+
106
+ const barrelPaths: string[] = [];
107
+ for (const { file: relPath } of barrelCandidates) {
108
+ if (!fileSymbols.has(relPath)) {
109
+ barrelPaths.push(path.join(rootDir, relPath));
110
+ }
111
+ }
112
+
113
+ if (barrelPaths.length === 0) return;
114
+
115
+ const deleteOutgoingEdges = db.prepare(
116
+ 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
117
+ );
118
+
119
+ try {
120
+ const barrelSymbols = await parseFilesAuto(barrelPaths, rootDir, engineOpts);
121
+ for (const [relPath, fileSym] of barrelSymbols) {
122
+ deleteOutgoingEdges.run(relPath);
123
+ fileSymbols.set(relPath, fileSym);
124
+ ctx.barrelOnlyFiles.add(relPath);
125
+ const reexports = fileSym.imports.filter((imp: Import) => imp.reexport);
126
+ if (reexports.length > 0) {
127
+ ctx.reexportMap.set(
128
+ relPath,
129
+ reexports.map((imp: Import) => ({
130
+ source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
131
+ names: imp.names,
132
+ wildcardReexport: imp.wildcardReexport || false,
133
+ })),
134
+ );
138
135
  }
139
136
  }
137
+ } catch (e: unknown) {
138
+ debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
139
+ }
140
+ }
141
+
142
+ export async function resolveImports(ctx: PipelineContext): Promise<void> {
143
+ const { fileSymbols, rootDir, aliases, allFiles, isFullBuild } = ctx;
144
+ const t0 = performance.now();
145
+
146
+ const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
147
+ for (const [relPath, symbols] of fileSymbols) {
148
+ const absFile = path.join(rootDir, relPath);
149
+ for (const imp of symbols.imports) {
150
+ batchInputs.push({ fromFile: absFile, importSource: imp.source });
151
+ }
152
+ }
153
+ ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
154
+ ctx.timing.resolveMs = performance.now() - t0;
155
+
156
+ buildReexportMap(ctx);
157
+
158
+ ctx.barrelOnlyFiles = new Set<string>();
159
+ if (!isFullBuild) {
160
+ const barrelCandidates = findBarrelCandidates(ctx);
161
+ await reparseBarrelFiles(ctx, barrelCandidates);
140
162
  }
141
163
  }
142
164
 
143
165
  export function getResolved(ctx: PipelineContext, absFile: string, importSource: string): string {
144
166
  if (ctx.batchResolved) {
145
- const key = `${absFile}|${importSource}`;
167
+ const key = `${normalizePath(absFile)}|${importSource}`;
146
168
  const hit = ctx.batchResolved.get(key);
147
169
  if (hit !== undefined) return hit;
148
170
  }