@optave/codegraph 3.7.0 → 3.8.1

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 (401) hide show
  1. package/README.md +28 -16
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +195 -30
  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/rules/javascript.d.ts.map +1 -1
  10. package/dist/ast-analysis/rules/javascript.js +0 -1
  11. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  12. package/dist/ast-analysis/shared.d.ts.map +1 -1
  13. package/dist/ast-analysis/shared.js +24 -19
  14. package/dist/ast-analysis/shared.js.map +1 -1
  15. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor-utils.js +55 -39
  17. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  18. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitor.js +91 -70
  20. package/dist/ast-analysis/visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/ast-store-visitor.js +52 -129
  23. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  26. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  27. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  28. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  29. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  30. package/dist/cli/commands/ast.js +2 -2
  31. package/dist/cli/commands/ast.js.map +1 -1
  32. package/dist/cli/commands/watch.d.ts.map +1 -1
  33. package/dist/cli/commands/watch.js +16 -2
  34. package/dist/cli/commands/watch.js.map +1 -1
  35. package/dist/db/connection.d.ts.map +1 -1
  36. package/dist/db/connection.js +29 -26
  37. package/dist/db/connection.js.map +1 -1
  38. package/dist/db/query-builder.d.ts.map +1 -1
  39. package/dist/db/query-builder.js +16 -5
  40. package/dist/db/query-builder.js.map +1 -1
  41. package/dist/db/repository/base.d.ts +10 -0
  42. package/dist/db/repository/base.d.ts.map +1 -1
  43. package/dist/db/repository/base.js +17 -0
  44. package/dist/db/repository/base.js.map +1 -1
  45. package/dist/db/repository/native-repository.d.ts +6 -1
  46. package/dist/db/repository/native-repository.d.ts.map +1 -1
  47. package/dist/db/repository/native-repository.js +77 -1
  48. package/dist/db/repository/native-repository.js.map +1 -1
  49. package/dist/db/repository/nodes.d.ts.map +1 -1
  50. package/dist/db/repository/nodes.js +8 -4
  51. package/dist/db/repository/nodes.js.map +1 -1
  52. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  53. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  54. package/dist/db/repository/sqlite-repository.js +26 -0
  55. package/dist/db/repository/sqlite-repository.js.map +1 -1
  56. package/dist/domain/analysis/brief.d.ts.map +1 -1
  57. package/dist/domain/analysis/brief.js +13 -17
  58. package/dist/domain/analysis/brief.js.map +1 -1
  59. package/dist/domain/analysis/context.d.ts.map +1 -1
  60. package/dist/domain/analysis/context.js +14 -11
  61. package/dist/domain/analysis/context.js.map +1 -1
  62. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  63. package/dist/domain/analysis/dependencies.js +53 -52
  64. package/dist/domain/analysis/dependencies.js.map +1 -1
  65. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  66. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  67. package/dist/domain/analysis/fn-impact.js +33 -31
  68. package/dist/domain/analysis/fn-impact.js.map +1 -1
  69. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  70. package/dist/domain/analysis/implementations.js +11 -19
  71. package/dist/domain/analysis/implementations.js.map +1 -1
  72. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  73. package/dist/domain/analysis/module-map.js +55 -76
  74. package/dist/domain/analysis/module-map.js.map +1 -1
  75. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  76. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  77. package/dist/domain/analysis/query-helpers.js +15 -1
  78. package/dist/domain/analysis/query-helpers.js.map +1 -1
  79. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  80. package/dist/domain/graph/builder/pipeline.js +315 -43
  81. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  83. package/dist/domain/graph/builder/stages/build-edges.js +106 -1
  84. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  85. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  86. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  87. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  88. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.js +99 -51
  90. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  91. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  92. package/dist/domain/graph/builder/stages/finalize.js +34 -7
  93. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  94. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  95. package/dist/domain/graph/builder/stages/insert-nodes.js +50 -26
  96. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  97. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  98. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  99. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  100. package/dist/domain/graph/cycles.d.ts +6 -0
  101. package/dist/domain/graph/cycles.d.ts.map +1 -1
  102. package/dist/domain/graph/cycles.js +114 -22
  103. package/dist/domain/graph/cycles.js.map +1 -1
  104. package/dist/domain/graph/resolve.js +1 -1
  105. package/dist/domain/graph/resolve.js.map +1 -1
  106. package/dist/domain/graph/watcher.d.ts +2 -0
  107. package/dist/domain/graph/watcher.d.ts.map +1 -1
  108. package/dist/domain/graph/watcher.js +170 -75
  109. package/dist/domain/graph/watcher.js.map +1 -1
  110. package/dist/domain/parser.d.ts +1 -6
  111. package/dist/domain/parser.d.ts.map +1 -1
  112. package/dist/domain/parser.js +101 -32
  113. package/dist/domain/parser.js.map +1 -1
  114. package/dist/domain/search/generator.js +1 -1
  115. package/dist/domain/search/generator.js.map +1 -1
  116. package/dist/domain/search/models.d.ts +4 -3
  117. package/dist/domain/search/models.d.ts.map +1 -1
  118. package/dist/domain/search/models.js +18 -5
  119. package/dist/domain/search/models.js.map +1 -1
  120. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  121. package/dist/domain/search/search/hybrid.js +29 -18
  122. package/dist/domain/search/search/hybrid.js.map +1 -1
  123. package/dist/extractors/clojure.d.ts +12 -0
  124. package/dist/extractors/clojure.d.ts.map +1 -0
  125. package/dist/extractors/clojure.js +245 -0
  126. package/dist/extractors/clojure.js.map +1 -0
  127. package/dist/extractors/cuda.d.ts +11 -0
  128. package/dist/extractors/cuda.d.ts.map +1 -0
  129. package/dist/extractors/cuda.js +302 -0
  130. package/dist/extractors/cuda.js.map +1 -0
  131. package/dist/extractors/erlang.d.ts +14 -0
  132. package/dist/extractors/erlang.d.ts.map +1 -0
  133. package/dist/extractors/erlang.js +239 -0
  134. package/dist/extractors/erlang.js.map +1 -0
  135. package/dist/extractors/fsharp.d.ts +13 -0
  136. package/dist/extractors/fsharp.d.ts.map +1 -0
  137. package/dist/extractors/fsharp.js +218 -0
  138. package/dist/extractors/fsharp.js.map +1 -0
  139. package/dist/extractors/gleam.d.ts +14 -0
  140. package/dist/extractors/gleam.d.ts.map +1 -0
  141. package/dist/extractors/gleam.js +229 -0
  142. package/dist/extractors/gleam.js.map +1 -0
  143. package/dist/extractors/go.js +36 -33
  144. package/dist/extractors/go.js.map +1 -1
  145. package/dist/extractors/groovy.d.ts +10 -0
  146. package/dist/extractors/groovy.d.ts.map +1 -0
  147. package/dist/extractors/groovy.js +304 -0
  148. package/dist/extractors/groovy.js.map +1 -0
  149. package/dist/extractors/helpers.d.ts.map +1 -1
  150. package/dist/extractors/helpers.js +40 -29
  151. package/dist/extractors/helpers.js.map +1 -1
  152. package/dist/extractors/index.d.ts +11 -0
  153. package/dist/extractors/index.d.ts.map +1 -1
  154. package/dist/extractors/index.js +11 -0
  155. package/dist/extractors/index.js.map +1 -1
  156. package/dist/extractors/java.js +58 -46
  157. package/dist/extractors/java.js.map +1 -1
  158. package/dist/extractors/javascript.js +46 -45
  159. package/dist/extractors/javascript.js.map +1 -1
  160. package/dist/extractors/julia.d.ts +16 -0
  161. package/dist/extractors/julia.d.ts.map +1 -0
  162. package/dist/extractors/julia.js +287 -0
  163. package/dist/extractors/julia.js.map +1 -0
  164. package/dist/extractors/kotlin.js +84 -78
  165. package/dist/extractors/kotlin.js.map +1 -1
  166. package/dist/extractors/objc.d.ts +9 -0
  167. package/dist/extractors/objc.d.ts.map +1 -0
  168. package/dist/extractors/objc.js +406 -0
  169. package/dist/extractors/objc.js.map +1 -0
  170. package/dist/extractors/ocaml.js +74 -0
  171. package/dist/extractors/ocaml.js.map +1 -1
  172. package/dist/extractors/python.js +29 -24
  173. package/dist/extractors/python.js.map +1 -1
  174. package/dist/extractors/r.d.ts +13 -0
  175. package/dist/extractors/r.d.ts.map +1 -0
  176. package/dist/extractors/r.js +251 -0
  177. package/dist/extractors/r.js.map +1 -0
  178. package/dist/extractors/rust.js +41 -32
  179. package/dist/extractors/rust.js.map +1 -1
  180. package/dist/extractors/solidity.d.ts +9 -0
  181. package/dist/extractors/solidity.d.ts.map +1 -0
  182. package/dist/extractors/solidity.js +365 -0
  183. package/dist/extractors/solidity.js.map +1 -0
  184. package/dist/extractors/swift.js +83 -81
  185. package/dist/extractors/swift.js.map +1 -1
  186. package/dist/extractors/verilog.d.ts +9 -0
  187. package/dist/extractors/verilog.d.ts.map +1 -0
  188. package/dist/extractors/verilog.js +286 -0
  189. package/dist/extractors/verilog.js.map +1 -0
  190. package/dist/extractors/zig.js +58 -60
  191. package/dist/extractors/zig.js.map +1 -1
  192. package/dist/features/ast.d.ts +16 -14
  193. package/dist/features/ast.d.ts.map +1 -1
  194. package/dist/features/ast.js +84 -83
  195. package/dist/features/ast.js.map +1 -1
  196. package/dist/features/audit.d.ts.map +1 -1
  197. package/dist/features/audit.js +8 -6
  198. package/dist/features/audit.js.map +1 -1
  199. package/dist/features/branch-compare.d.ts.map +1 -1
  200. package/dist/features/branch-compare.js +69 -72
  201. package/dist/features/branch-compare.js.map +1 -1
  202. package/dist/features/communities.d.ts.map +1 -1
  203. package/dist/features/communities.js +19 -7
  204. package/dist/features/communities.js.map +1 -1
  205. package/dist/features/complexity.d.ts.map +1 -1
  206. package/dist/features/complexity.js +120 -125
  207. package/dist/features/complexity.js.map +1 -1
  208. package/dist/features/dataflow.d.ts.map +1 -1
  209. package/dist/features/dataflow.js +136 -137
  210. package/dist/features/dataflow.js.map +1 -1
  211. package/dist/features/flow.d.ts.map +1 -1
  212. package/dist/features/flow.js +84 -79
  213. package/dist/features/flow.js.map +1 -1
  214. package/dist/features/structure-query.d.ts.map +1 -1
  215. package/dist/features/structure-query.js +69 -65
  216. package/dist/features/structure-query.js.map +1 -1
  217. package/dist/graph/algorithms/bfs.d.ts +2 -0
  218. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  219. package/dist/graph/algorithms/bfs.js +27 -0
  220. package/dist/graph/algorithms/bfs.js.map +1 -1
  221. package/dist/graph/algorithms/centrality.d.ts +2 -0
  222. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  223. package/dist/graph/algorithms/centrality.js +28 -0
  224. package/dist/graph/algorithms/centrality.js.map +1 -1
  225. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  226. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  227. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  228. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  229. package/dist/graph/algorithms/leiden/partition.js +288 -266
  230. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  231. package/dist/graph/algorithms/louvain.d.ts +3 -4
  232. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  233. package/dist/graph/algorithms/louvain.js +29 -0
  234. package/dist/graph/algorithms/louvain.js.map +1 -1
  235. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  236. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  237. package/dist/graph/algorithms/shortest-path.js +18 -1
  238. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  239. package/dist/graph/model.d.ts.map +1 -1
  240. package/dist/graph/model.js +5 -1
  241. package/dist/graph/model.js.map +1 -1
  242. package/dist/infrastructure/config.d.ts.map +1 -1
  243. package/dist/infrastructure/config.js +6 -4
  244. package/dist/infrastructure/config.js.map +1 -1
  245. package/dist/infrastructure/suppress.d.ts +25 -0
  246. package/dist/infrastructure/suppress.d.ts.map +1 -0
  247. package/dist/infrastructure/suppress.js +43 -0
  248. package/dist/infrastructure/suppress.js.map +1 -0
  249. package/dist/mcp/server.d.ts.map +1 -1
  250. package/dist/mcp/server.js +29 -24
  251. package/dist/mcp/server.js.map +1 -1
  252. package/dist/presentation/dataflow.d.ts.map +1 -1
  253. package/dist/presentation/dataflow.js +47 -38
  254. package/dist/presentation/dataflow.js.map +1 -1
  255. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  256. package/dist/presentation/diff-impact-mermaid.js +60 -51
  257. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  258. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  259. package/dist/presentation/queries-cli/exports.js +20 -14
  260. package/dist/presentation/queries-cli/exports.js.map +1 -1
  261. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  262. package/dist/presentation/queries-cli/impact.js +15 -13
  263. package/dist/presentation/queries-cli/impact.js.map +1 -1
  264. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  265. package/dist/presentation/queries-cli/inspect.js +101 -79
  266. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  267. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  268. package/dist/presentation/queries-cli/overview.js +25 -16
  269. package/dist/presentation/queries-cli/overview.js.map +1 -1
  270. package/dist/presentation/queries-cli/path.js +26 -20
  271. package/dist/presentation/queries-cli/path.js.map +1 -1
  272. package/dist/presentation/result-formatter.d.ts +10 -0
  273. package/dist/presentation/result-formatter.d.ts.map +1 -1
  274. package/dist/presentation/result-formatter.js +16 -1
  275. package/dist/presentation/result-formatter.js.map +1 -1
  276. package/dist/presentation/viewer.d.ts.map +1 -1
  277. package/dist/presentation/viewer.js +18 -12
  278. package/dist/presentation/viewer.js.map +1 -1
  279. package/dist/shared/errors.d.ts +5 -0
  280. package/dist/shared/errors.d.ts.map +1 -1
  281. package/dist/shared/errors.js +5 -0
  282. package/dist/shared/errors.js.map +1 -1
  283. package/dist/shared/hierarchy.d.ts +8 -2
  284. package/dist/shared/hierarchy.d.ts.map +1 -1
  285. package/dist/shared/hierarchy.js +42 -1
  286. package/dist/shared/hierarchy.js.map +1 -1
  287. package/dist/shared/normalize.d.ts +6 -1
  288. package/dist/shared/normalize.d.ts.map +1 -1
  289. package/dist/shared/normalize.js +20 -12
  290. package/dist/shared/normalize.js.map +1 -1
  291. package/dist/shared/paginate.d.ts +0 -9
  292. package/dist/shared/paginate.d.ts.map +1 -1
  293. package/dist/shared/paginate.js +0 -15
  294. package/dist/shared/paginate.js.map +1 -1
  295. package/dist/types.d.ts +129 -3
  296. package/dist/types.d.ts.map +1 -1
  297. package/grammars/tree-sitter-clojure.wasm +0 -0
  298. package/grammars/tree-sitter-cuda.wasm +0 -0
  299. package/grammars/tree-sitter-erlang.wasm +0 -0
  300. package/grammars/tree-sitter-fsharp.wasm +0 -0
  301. package/grammars/tree-sitter-gleam.wasm +0 -0
  302. package/grammars/tree-sitter-groovy.wasm +0 -0
  303. package/grammars/tree-sitter-julia.wasm +0 -0
  304. package/grammars/tree-sitter-objc.wasm +0 -0
  305. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  306. package/grammars/tree-sitter-r.wasm +0 -0
  307. package/grammars/tree-sitter-solidity.wasm +0 -0
  308. package/grammars/tree-sitter-verilog.wasm +0 -0
  309. package/package.json +18 -7
  310. package/src/ast-analysis/engine.ts +245 -42
  311. package/src/ast-analysis/metrics.ts +33 -11
  312. package/src/ast-analysis/rules/javascript.ts +0 -1
  313. package/src/ast-analysis/shared.ts +33 -24
  314. package/src/ast-analysis/visitor-utils.ts +52 -32
  315. package/src/ast-analysis/visitor.ts +132 -71
  316. package/src/ast-analysis/visitors/ast-store-visitor.ts +49 -119
  317. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  318. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  319. package/src/cli/commands/ast.ts +2 -2
  320. package/src/cli/commands/watch.ts +16 -2
  321. package/src/db/connection.ts +29 -28
  322. package/src/db/query-builder.ts +15 -3
  323. package/src/db/repository/base.ts +20 -0
  324. package/src/db/repository/native-repository.ts +79 -1
  325. package/src/db/repository/nodes.ts +13 -8
  326. package/src/db/repository/sqlite-repository.ts +29 -0
  327. package/src/domain/analysis/brief.ts +15 -25
  328. package/src/domain/analysis/context.ts +17 -10
  329. package/src/domain/analysis/dependencies.ts +67 -76
  330. package/src/domain/analysis/fn-impact.ts +36 -43
  331. package/src/domain/analysis/implementations.ts +11 -17
  332. package/src/domain/analysis/module-map.ts +58 -92
  333. package/src/domain/analysis/query-helpers.ts +18 -1
  334. package/src/domain/graph/builder/pipeline.ts +366 -41
  335. package/src/domain/graph/builder/stages/build-edges.ts +162 -1
  336. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  337. package/src/domain/graph/builder/stages/detect-changes.ts +110 -56
  338. package/src/domain/graph/builder/stages/finalize.ts +41 -11
  339. package/src/domain/graph/builder/stages/insert-nodes.ts +75 -39
  340. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  341. package/src/domain/graph/cycles.ts +110 -23
  342. package/src/domain/graph/resolve.ts +1 -1
  343. package/src/domain/graph/watcher.ts +202 -96
  344. package/src/domain/parser.ts +122 -28
  345. package/src/domain/search/generator.ts +1 -1
  346. package/src/domain/search/models.ts +17 -4
  347. package/src/domain/search/search/hybrid.ts +69 -51
  348. package/src/extractors/clojure.ts +273 -0
  349. package/src/extractors/cuda.ts +316 -0
  350. package/src/extractors/erlang.ts +252 -0
  351. package/src/extractors/fsharp.ts +253 -0
  352. package/src/extractors/gleam.ts +246 -0
  353. package/src/extractors/go.ts +43 -33
  354. package/src/extractors/groovy.ts +332 -0
  355. package/src/extractors/helpers.ts +37 -23
  356. package/src/extractors/index.ts +11 -0
  357. package/src/extractors/java.ts +66 -47
  358. package/src/extractors/javascript.ts +45 -46
  359. package/src/extractors/julia.ts +318 -0
  360. package/src/extractors/kotlin.ts +84 -77
  361. package/src/extractors/objc.ts +431 -0
  362. package/src/extractors/ocaml.ts +78 -0
  363. package/src/extractors/python.ts +31 -25
  364. package/src/extractors/r.ts +253 -0
  365. package/src/extractors/rust.ts +37 -29
  366. package/src/extractors/solidity.ts +394 -0
  367. package/src/extractors/swift.ts +81 -80
  368. package/src/extractors/verilog.ts +315 -0
  369. package/src/extractors/zig.ts +58 -61
  370. package/src/features/ast.ts +131 -112
  371. package/src/features/audit.ts +8 -6
  372. package/src/features/branch-compare.ts +105 -79
  373. package/src/features/communities.ts +25 -10
  374. package/src/features/complexity.ts +171 -134
  375. package/src/features/dataflow.ts +165 -175
  376. package/src/features/flow.ts +129 -92
  377. package/src/features/structure-query.ts +79 -64
  378. package/src/graph/algorithms/bfs.ts +34 -0
  379. package/src/graph/algorithms/centrality.ts +30 -0
  380. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  381. package/src/graph/algorithms/leiden/partition.ts +359 -294
  382. package/src/graph/algorithms/louvain.ts +31 -4
  383. package/src/graph/algorithms/shortest-path.ts +20 -1
  384. package/src/graph/model.ts +6 -1
  385. package/src/infrastructure/config.ts +6 -4
  386. package/src/infrastructure/suppress.ts +47 -0
  387. package/src/mcp/server.ts +53 -37
  388. package/src/presentation/dataflow.ts +50 -44
  389. package/src/presentation/diff-impact-mermaid.ts +104 -62
  390. package/src/presentation/queries-cli/exports.ts +21 -13
  391. package/src/presentation/queries-cli/impact.ts +15 -13
  392. package/src/presentation/queries-cli/inspect.ts +100 -81
  393. package/src/presentation/queries-cli/overview.ts +26 -16
  394. package/src/presentation/queries-cli/path.ts +33 -25
  395. package/src/presentation/result-formatter.ts +19 -1
  396. package/src/presentation/viewer.ts +42 -14
  397. package/src/shared/errors.ts +6 -0
  398. package/src/shared/hierarchy.ts +50 -2
  399. package/src/shared/normalize.ts +31 -12
  400. package/src/shared/paginate.ts +0 -17
  401. package/src/types.ts +138 -3
@@ -6,12 +6,21 @@
6
6
  */
7
7
  import path from 'node:path';
8
8
  import { performance } from 'node:perf_hooks';
9
- import { closeDbPair, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js';
9
+ import {
10
+ closeDbPair,
11
+ getBuildMeta,
12
+ initSchema,
13
+ MIGRATIONS,
14
+ openDb,
15
+ setBuildMeta,
16
+ } from '../../../db/index.js';
10
17
  import { detectWorkspaces, loadConfig } from '../../../infrastructure/config.js';
11
- import { info, warn } from '../../../infrastructure/logger.js';
18
+ import { debug, info, warn } from '../../../infrastructure/logger.js';
12
19
  import { loadNative } from '../../../infrastructure/native.js';
20
+ import { semverCompare } from '../../../infrastructure/update-check.js';
21
+ import { toErrorMessage } from '../../../shared/errors.js';
13
22
  import { CODEGRAPH_VERSION } from '../../../shared/version.js';
14
- import type { BuildGraphOpts, BuildResult } from '../../../types.js';
23
+ import type { BuildGraphOpts, BuildResult, Definition, ExtractorOutput } from '../../../types.js';
15
24
  import { getActiveEngine } from '../../parser.js';
16
25
  import { setWorkspaces } from '../resolve.js';
17
26
  import { PipelineContext } from './context.js';
@@ -35,18 +44,28 @@ function initializeEngine(ctx: PipelineContext): void {
35
44
  dataflow: ctx.opts.dataflow !== false,
36
45
  ast: ctx.opts.ast !== false,
37
46
  nativeDb: ctx.nativeDb,
38
- // WAL checkpoint callbacks for dual-connection WAL guard (#696).
47
+ // WAL checkpoint callbacks for dual-connection WAL guard (#696, #715).
39
48
  // Feature modules (ast, cfg, complexity, dataflow) receive `db` as a
40
49
  // parameter and cannot tolerate close/reopen (stale reference). Instead,
41
- // checkpoint the WAL so native writes start with a clean slate. Features
42
- // return early on native success and never read native-written WAL data
43
- // through the JS connection, so a post-write checkpoint is unnecessary.
50
+ // checkpoint the WAL so native writes start with a clean slate.
51
+ // After native writes, resumeJsDb checkpoints through rusqlite so
52
+ // better-sqlite3 never reads WAL frames from a different SQLite library.
44
53
  suspendJsDb: ctx.nativeDb
45
54
  ? () => {
46
55
  ctx.db.pragma('wal_checkpoint(TRUNCATE)');
47
56
  }
48
57
  : undefined,
49
- resumeJsDb: ctx.nativeDb ? () => {} : undefined,
58
+ resumeJsDb: ctx.nativeDb
59
+ ? () => {
60
+ try {
61
+ ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
62
+ } catch (e) {
63
+ debug(
64
+ `resumeJsDb: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
65
+ );
66
+ }
67
+ }
68
+ : undefined,
50
69
  };
51
70
  const { name: engineName, version: engineVersion } = getActiveEngine(ctx.engineOpts);
52
71
  ctx.engineName = engineName as 'native' | 'wasm';
@@ -120,8 +139,16 @@ function setupPipeline(ctx: PipelineContext): void {
120
139
  try {
121
140
  ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
122
141
  ctx.nativeDb.initSchema();
142
+ // Checkpoint WAL through rusqlite so better-sqlite3 sees a clean DB
143
+ // with no cross-library WAL frames (#715, #717).
144
+ ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
123
145
  } catch (err) {
124
- warn(`NativeDatabase init failed, falling back to JS: ${(err as Error).message}`);
146
+ warn(`NativeDatabase setup failed, falling back to JS: ${toErrorMessage(err)}`);
147
+ try {
148
+ ctx.nativeDb?.close();
149
+ } catch (e) {
150
+ debug(`setupNativeDb: close failed during fallback: ${toErrorMessage(e)}`);
151
+ }
125
152
  ctx.nativeDb = undefined;
126
153
  }
127
154
  // Always run JS initSchema so better-sqlite3 sees the schema —
@@ -169,6 +196,58 @@ function formatTimingResult(ctx: PipelineContext): BuildResult {
169
196
  };
170
197
  }
171
198
 
199
+ // ── NativeDb lifecycle helpers ──────────────────────────────────────────
200
+
201
+ /** Checkpoint WAL through rusqlite and close the native connection. */
202
+ function closeNativeDb(ctx: PipelineContext, label: string): void {
203
+ if (!ctx.nativeDb) return;
204
+ try {
205
+ ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
206
+ } catch (e) {
207
+ debug(`${label} WAL checkpoint failed: ${toErrorMessage(e)}`);
208
+ }
209
+ try {
210
+ ctx.nativeDb.close();
211
+ } catch (e) {
212
+ debug(`${label} nativeDb close failed: ${toErrorMessage(e)}`);
213
+ }
214
+ ctx.nativeDb = undefined;
215
+ }
216
+
217
+ /** Try to reopen the native connection for a given pipeline phase. */
218
+ function reopenNativeDb(ctx: PipelineContext, label: string): void {
219
+ const native = loadNative();
220
+ if (!native?.NativeDatabase) return;
221
+ try {
222
+ ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
223
+ } catch (e) {
224
+ debug(`reopen nativeDb for ${label} failed: ${toErrorMessage(e)}`);
225
+ ctx.nativeDb = undefined;
226
+ }
227
+ }
228
+
229
+ /** Close nativeDb and clear stale references in engineOpts. */
230
+ function suspendNativeDb(ctx: PipelineContext, label: string): void {
231
+ closeNativeDb(ctx, label);
232
+ if (ctx.engineOpts?.nativeDb) {
233
+ ctx.engineOpts.nativeDb = undefined;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * After native writes, reopen the JS db connection to get a fresh page cache.
239
+ * Rusqlite WAL truncation invalidates better-sqlite3's internal WAL index,
240
+ * causing SQLITE_CORRUPT on the next read (#715, #736).
241
+ */
242
+ function refreshJsDb(ctx: PipelineContext): void {
243
+ try {
244
+ ctx.db.close();
245
+ } catch (e) {
246
+ debug(`refreshJsDb close failed: ${toErrorMessage(e)}`);
247
+ }
248
+ ctx.db = openDb(ctx.dbPath);
249
+ }
250
+
172
251
  // ── Pipeline stages execution ───────────────────────────────────────────
173
252
 
174
253
  async function runPipelineStages(ctx: PipelineContext): Promise<void> {
@@ -179,17 +258,7 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
179
258
  // that use suspendJsDb/resumeJsDb WAL checkpoint pattern (#696).
180
259
  const hadNativeDb = !!ctx.nativeDb;
181
260
  if (ctx.db && ctx.nativeDb) {
182
- try {
183
- ctx.nativeDb.close();
184
- } catch {
185
- /* ignore close errors */
186
- }
187
- ctx.nativeDb = undefined;
188
- // Also clear stale reference in engineOpts to prevent stages from
189
- // calling methods on the closed NativeDatabase.
190
- if (ctx.engineOpts?.nativeDb) {
191
- ctx.engineOpts.nativeDb = undefined;
192
- }
261
+ suspendNativeDb(ctx, 'pre-collect');
193
262
  }
194
263
 
195
264
  await collectFiles(ctx);
@@ -198,7 +267,22 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
198
267
  if (ctx.earlyExit) return;
199
268
 
200
269
  await parseFiles(ctx);
270
+
271
+ // Temporarily reopen nativeDb for insertNodes — it uses the WAL checkpoint
272
+ // guard internally (same pattern as feature modules). Closed again before
273
+ // resolveImports/buildEdges which don't yet have the guard (#709).
274
+ if (hadNativeDb && ctx.engineName === 'native') {
275
+ reopenNativeDb(ctx, 'insertNodes');
276
+ }
277
+
201
278
  await insertNodes(ctx);
279
+
280
+ // Close nativeDb after insertNodes — remaining pipeline stages use JS paths.
281
+ if (ctx.nativeDb && ctx.db) {
282
+ closeNativeDb(ctx, 'post-insertNodes');
283
+ refreshJsDb(ctx);
284
+ }
285
+
202
286
  await resolveImports(ctx);
203
287
  await buildEdges(ctx);
204
288
  await buildStructure(ctx);
@@ -206,33 +290,23 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
206
290
  // Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow)
207
291
  // which use suspendJsDb/resumeJsDb WAL checkpoint before native writes.
208
292
  if (hadNativeDb) {
209
- const native = loadNative();
210
- if (native?.NativeDatabase) {
211
- try {
212
- ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
213
- if (ctx.engineOpts) {
214
- ctx.engineOpts.nativeDb = ctx.nativeDb;
215
- }
216
- } catch {
217
- ctx.nativeDb = undefined;
218
- if (ctx.engineOpts) {
219
- ctx.engineOpts.nativeDb = undefined;
220
- }
221
- }
293
+ reopenNativeDb(ctx, 'analyses');
294
+ if (ctx.nativeDb && ctx.engineOpts) {
295
+ ctx.engineOpts.nativeDb = ctx.nativeDb;
296
+ }
297
+ if (!ctx.nativeDb && ctx.engineOpts) {
298
+ ctx.engineOpts.nativeDb = undefined;
222
299
  }
223
300
  }
224
301
 
225
302
  await runAnalyses(ctx);
226
303
 
227
- // Close nativeDb after analyses finalize uses JS paths for setBuildMeta
228
- // and closeDbPair handles cleanup. Avoids dual-connection during finalize.
304
+ // Keep nativeDb open through finalize so persistBuildMetadata, advisory
305
+ // checks, and count queries use the native path. closeDbPair inside
306
+ // finalize handles both connections. Refresh the JS db so it has a
307
+ // valid page cache in case finalize falls back to JS paths (#751).
229
308
  if (ctx.nativeDb) {
230
- try {
231
- ctx.nativeDb.close();
232
- } catch {
233
- /* ignore close errors */
234
- }
235
- ctx.nativeDb = undefined;
309
+ refreshJsDb(ctx);
236
310
  }
237
311
 
238
312
  await finalize(ctx);
@@ -256,6 +330,257 @@ export async function buildGraph(
256
330
 
257
331
  try {
258
332
  setupPipeline(ctx);
333
+
334
+ // ── Rust orchestrator fast path (#695) ────────────────────────────
335
+ // When available, run the entire build pipeline in Rust with zero
336
+ // napi crossings (eliminates WAL dual-connection dance). Falls back
337
+ // to the JS pipeline on failure or when native is unavailable.
338
+ //
339
+ // Native addon 3.8.0 has a path bug: file_symbols keys are absolute
340
+ // paths but known_files are relative, causing zero import/call edges.
341
+ // Skip the orchestrator for affected versions (fixed in 3.9.0+).
342
+ const orchestratorBuggy = !!ctx.engineVersion && semverCompare(ctx.engineVersion, '3.8.0') <= 0;
343
+ const forceJs =
344
+ process.env.CODEGRAPH_FORCE_JS_PIPELINE === '1' ||
345
+ ctx.forceFullRebuild ||
346
+ orchestratorBuggy ||
347
+ ctx.engineName !== 'native';
348
+ if (forceJs) {
349
+ const reason =
350
+ process.env.CODEGRAPH_FORCE_JS_PIPELINE === '1'
351
+ ? 'CODEGRAPH_FORCE_JS_PIPELINE=1'
352
+ : ctx.forceFullRebuild
353
+ ? 'forceFullRebuild'
354
+ : orchestratorBuggy
355
+ ? `buggy addon ${ctx.engineVersion}`
356
+ : `engine=${ctx.engineName}`;
357
+ debug(`Skipping native orchestrator: ${reason}`);
358
+ }
359
+ if (!forceJs && ctx.nativeDb?.buildGraph) {
360
+ try {
361
+ const resultJson = ctx.nativeDb.buildGraph(
362
+ ctx.rootDir,
363
+ JSON.stringify(ctx.config),
364
+ JSON.stringify(ctx.aliases),
365
+ JSON.stringify(opts),
366
+ );
367
+ const result = JSON.parse(resultJson) as {
368
+ phases: Record<string, number>;
369
+ earlyExit?: boolean;
370
+ nodeCount?: number;
371
+ edgeCount?: number;
372
+ fileCount?: number;
373
+ changedFiles?: string[];
374
+ changedCount?: number;
375
+ removedCount?: number;
376
+ isFullBuild?: boolean;
377
+ };
378
+
379
+ if (result.earlyExit) {
380
+ info('No changes detected');
381
+ closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
382
+ return;
383
+ }
384
+
385
+ // Log incremental status to match JS pipeline output
386
+ const changed = result.changedCount ?? 0;
387
+ const removed = result.removedCount ?? 0;
388
+ if (!result.isFullBuild && (changed > 0 || removed > 0)) {
389
+ info(`Incremental: ${changed} changed, ${removed} removed`);
390
+ }
391
+
392
+ // Map Rust timing fields to the JS BuildResult format.
393
+ // Rust handles collect+detect+parse+insert+resolve+edges+structure+roles.
394
+ const p = result.phases;
395
+
396
+ // Sync build_meta so JS-side version/engine checks work on next build.
397
+ // Note: the Rust orchestrator also writes codegraph_version (using
398
+ // CARGO_PKG_VERSION). We intentionally overwrite it here with the npm
399
+ // package version so that the JS-side "version changed → full rebuild"
400
+ // detection (line ~97) compares against the authoritative JS version.
401
+ // The two versions are kept in lockstep by the release process.
402
+ setBuildMeta(ctx.db, {
403
+ engine: ctx.engineName,
404
+ engine_version: ctx.engineVersion || '',
405
+ codegraph_version: CODEGRAPH_VERSION,
406
+ schema_version: String(ctx.schemaVersion),
407
+ built_at: new Date().toISOString(),
408
+ node_count: String(result.nodeCount ?? 0),
409
+ edge_count: String(result.edgeCount ?? 0),
410
+ });
411
+
412
+ info(
413
+ `Native build orchestrator completed: ${result.nodeCount ?? 0} nodes, ${result.edgeCount ?? 0} edges, ${result.fileCount ?? 0} files`,
414
+ );
415
+
416
+ // ── Run analysis phases (AST, complexity, CFG, dataflow) ──────
417
+ // Not yet ported to Rust. After the native orchestrator finishes,
418
+ // reconstruct a minimal fileSymbols map from the DB and run analyses
419
+ // via the JS engine (native standalone functions + WASM fallback).
420
+ let analysisTiming = { astMs: 0, complexityMs: 0, cfgMs: 0, dataflowMs: 0 };
421
+ const needsAnalysis =
422
+ opts.ast !== false ||
423
+ opts.complexity !== false ||
424
+ opts.cfg !== false ||
425
+ opts.dataflow !== false;
426
+
427
+ if (needsAnalysis) {
428
+ // WAL handoff: checkpoint through rusqlite, close nativeDb,
429
+ // reopen better-sqlite3 with a fresh page cache (#715, #736).
430
+ try {
431
+ ctx.nativeDb!.exec('PRAGMA wal_checkpoint(TRUNCATE)');
432
+ } catch {
433
+ /* ignore checkpoint errors */
434
+ }
435
+ try {
436
+ ctx.nativeDb!.close();
437
+ } catch {
438
+ /* ignore close errors */
439
+ }
440
+ ctx.nativeDb = undefined;
441
+ try {
442
+ ctx.db.close();
443
+ } catch {
444
+ /* ignore close errors */
445
+ }
446
+ ctx.db = null!; // avoid closeDbPair operating on a stale handle
447
+ try {
448
+ ctx.db = openDb(ctx.dbPath);
449
+ } catch (reopenErr) {
450
+ warn(
451
+ `Failed to reopen DB for analysis after native build: ${(reopenErr as Error).message}`,
452
+ );
453
+ // Native build succeeded but we can't run analyses — return partial result
454
+ return {
455
+ phases: {
456
+ setupMs: +((p.setupMs ?? 0) + (p.collectMs ?? 0) + (p.detectMs ?? 0)).toFixed(1),
457
+ parseMs: +(p.parseMs ?? 0).toFixed(1),
458
+ insertMs: +(p.insertMs ?? 0).toFixed(1),
459
+ resolveMs: +(p.resolveMs ?? 0).toFixed(1),
460
+ edgesMs: +(p.edgesMs ?? 0).toFixed(1),
461
+ structureMs: +(p.structureMs ?? 0).toFixed(1),
462
+ rolesMs: +(p.rolesMs ?? 0).toFixed(1),
463
+ astMs: 0,
464
+ complexityMs: 0,
465
+ cfgMs: 0,
466
+ dataflowMs: 0,
467
+ finalizeMs: +(p.finalizeMs ?? 0).toFixed(1),
468
+ },
469
+ };
470
+ }
471
+
472
+ // Reconstruct minimal fileSymbols from DB for analysis visitors.
473
+ // Each entry needs definitions with name/kind/line/endLine so the
474
+ // engine can match complexity/CFG results to the right functions.
475
+ // For incremental builds, scope to only the files that were parsed
476
+ // in this cycle (matching the JS pipeline's behaviour in run-analyses.ts).
477
+ const changedFiles = result.changedFiles;
478
+ let query =
479
+ 'SELECT file, name, kind, line, end_line as endLine FROM nodes WHERE file IS NOT NULL';
480
+ const params: string[] = [];
481
+ if (changedFiles && changedFiles.length > 0) {
482
+ const placeholders = changedFiles.map(() => '?').join(',');
483
+ query += ` AND file IN (${placeholders})`;
484
+ params.push(...changedFiles);
485
+ }
486
+ query += ' ORDER BY file, line';
487
+ const rows = ctx.db.prepare(query).all(...params) as {
488
+ file: string;
489
+ name: string;
490
+ kind: string;
491
+ line: number;
492
+ endLine: number | null;
493
+ }[];
494
+
495
+ const fileSymbols = new Map<string, ExtractorOutput>();
496
+ for (const row of rows) {
497
+ let entry = fileSymbols.get(row.file);
498
+ if (!entry) {
499
+ entry = {
500
+ definitions: [],
501
+ calls: [],
502
+ imports: [],
503
+ classes: [],
504
+ exports: [],
505
+ typeMap: new Map(),
506
+ };
507
+ fileSymbols.set(row.file, entry);
508
+ }
509
+ entry.definitions.push({
510
+ name: row.name,
511
+ kind: row.kind as Definition['kind'],
512
+ line: row.line,
513
+ endLine: row.endLine ?? undefined,
514
+ });
515
+ }
516
+
517
+ // Reopen nativeDb for analysis features (suspend/resume WAL pattern).
518
+ const native = loadNative();
519
+ if (native?.NativeDatabase) {
520
+ try {
521
+ ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
522
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = ctx.nativeDb;
523
+ } catch {
524
+ ctx.nativeDb = undefined;
525
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
526
+ }
527
+ }
528
+
529
+ try {
530
+ const { runAnalyses: runAnalysesFn } = await import('../../../ast-analysis/engine.js');
531
+ analysisTiming = await runAnalysesFn(
532
+ ctx.db,
533
+ fileSymbols,
534
+ ctx.rootDir,
535
+ opts,
536
+ ctx.engineOpts,
537
+ );
538
+ } catch (err) {
539
+ warn(`Analysis phases failed after native build: ${toErrorMessage(err)}`);
540
+ }
541
+
542
+ // Close nativeDb after analyses
543
+ if (ctx.nativeDb) {
544
+ try {
545
+ ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
546
+ } catch {
547
+ /* ignore checkpoint errors */
548
+ }
549
+ try {
550
+ ctx.nativeDb.close();
551
+ } catch {
552
+ /* ignore close errors */
553
+ }
554
+ ctx.nativeDb = undefined;
555
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
556
+ }
557
+ }
558
+
559
+ closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
560
+ return {
561
+ phases: {
562
+ setupMs: +((p.setupMs ?? 0) + (p.collectMs ?? 0) + (p.detectMs ?? 0)).toFixed(1),
563
+ parseMs: +(p.parseMs ?? 0).toFixed(1),
564
+ insertMs: +(p.insertMs ?? 0).toFixed(1),
565
+ resolveMs: +(p.resolveMs ?? 0).toFixed(1),
566
+ edgesMs: +(p.edgesMs ?? 0).toFixed(1),
567
+ structureMs: +(p.structureMs ?? 0).toFixed(1),
568
+ rolesMs: +(p.rolesMs ?? 0).toFixed(1),
569
+ astMs: +(analysisTiming.astMs ?? 0).toFixed(1),
570
+ complexityMs: +(analysisTiming.complexityMs ?? 0).toFixed(1),
571
+ cfgMs: +(analysisTiming.cfgMs ?? 0).toFixed(1),
572
+ dataflowMs: +(analysisTiming.dataflowMs ?? 0).toFixed(1),
573
+ finalizeMs: +(p.finalizeMs ?? 0).toFixed(1),
574
+ },
575
+ };
576
+ } catch (err) {
577
+ warn(
578
+ `Native build orchestrator failed, falling back to JS pipeline: ${toErrorMessage(err)}`,
579
+ );
580
+ // Fall through to JS pipeline
581
+ }
582
+ }
583
+
259
584
  await runPipelineStages(ctx);
260
585
  } catch (err) {
261
586
  if (!ctx.earlyExit && ctx.db) {
@@ -155,6 +155,146 @@ function buildBarrelEdges(
155
155
  }
156
156
  }
157
157
 
158
+ // ── Import edges (native engine) ────────────────────────────────────────
159
+
160
+ function buildImportEdgesNative(
161
+ ctx: PipelineContext,
162
+ getNodeIdStmt: NodeIdStmt,
163
+ allEdgeRows: EdgeRowTuple[],
164
+ native: NativeAddon,
165
+ ): void {
166
+ const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
167
+
168
+ // 1. Build per-file input data
169
+ const files: Array<{
170
+ file: string;
171
+ fileNodeId: number;
172
+ isBarrelOnly: boolean;
173
+ imports: Array<{
174
+ source: string;
175
+ names: string[];
176
+ reexport: boolean;
177
+ typeOnly: boolean;
178
+ dynamicImport: boolean;
179
+ wildcardReexport: boolean;
180
+ }>;
181
+ definitionNames: string[];
182
+ }> = [];
183
+
184
+ // Collect all file node IDs we'll need (sources + targets)
185
+ const fileNodeIds: Array<{ file: string; nodeId: number }> = [];
186
+ const seenNodeFiles = new Set<string>();
187
+
188
+ const addFileNodeId = (relPath: string): { id: number } | undefined => {
189
+ if (seenNodeFiles.has(relPath)) return fileNodeRowCache.get(relPath);
190
+ const row = getNodeIdStmt.get(relPath, 'file', relPath, 0);
191
+ if (row) {
192
+ seenNodeFiles.add(relPath);
193
+ fileNodeIds.push({ file: relPath, nodeId: row.id });
194
+ fileNodeRowCache.set(relPath, row);
195
+ }
196
+ return row;
197
+ };
198
+ const fileNodeRowCache = new Map<string, { id: number }>();
199
+
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, '/');
207
+
208
+ for (const [relPath, symbols] of fileSymbols) {
209
+ const fileNodeRow = addFileNodeId(relPath);
210
+ if (!fileNodeRow) continue;
211
+
212
+ const importInfos: Array<{
213
+ source: string;
214
+ names: string[];
215
+ reexport: boolean;
216
+ typeOnly: boolean;
217
+ dynamicImport: boolean;
218
+ wildcardReexport: boolean;
219
+ }> = [];
220
+
221
+ for (const imp of symbols.imports) {
222
+ // Pre-resolve and register target file node
223
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
224
+ addFileNodeId(resolvedPath);
225
+
226
+ // Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
227
+ resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
228
+
229
+ importInfos.push({
230
+ source: imp.source,
231
+ names: imp.names,
232
+ reexport: !!imp.reexport,
233
+ typeOnly: !!imp.typeOnly,
234
+ dynamicImport: !!imp.dynamicImport,
235
+ wildcardReexport: !!imp.wildcardReexport,
236
+ });
237
+ }
238
+
239
+ files.push({
240
+ file: relPath,
241
+ fileNodeId: fileNodeRow.id,
242
+ isBarrelOnly: barrelOnlyFiles.has(relPath),
243
+ imports: importInfos,
244
+ definitionNames: symbols.definitions.map((d) => d.name),
245
+ });
246
+ }
247
+
248
+ // 4. Flatten reexportMap
249
+ const fileReexports: Array<{
250
+ file: string;
251
+ reexports: Array<{
252
+ source: string;
253
+ names: string[];
254
+ wildcardReexport: boolean;
255
+ }>;
256
+ }> = [];
257
+ if (ctx.reexportMap) {
258
+ for (const [file, entries] of ctx.reexportMap) {
259
+ const reexports = (
260
+ entries as Array<{ source: string; names: string[]; wildcardReexport: boolean }>
261
+ ).map((re) => ({
262
+ source: re.source,
263
+ names: re.names,
264
+ wildcardReexport: !!re.wildcardReexport,
265
+ }));
266
+ fileReexports.push({ file, reexports });
267
+
268
+ // Register reexport target files for node ID lookup
269
+ for (const re of reexports) {
270
+ addFileNodeId(re.source);
271
+ }
272
+ }
273
+ }
274
+
275
+ // 5. Compute barrel file list
276
+ const barrelFiles: string[] = [];
277
+ for (const [relPath] of fileSymbols) {
278
+ if (isBarrelFile(ctx, relPath)) {
279
+ barrelFiles.push(relPath);
280
+ }
281
+ }
282
+
283
+ // 6. Call native
284
+ const nativeEdges = native.buildImportEdges!(
285
+ files,
286
+ resolvedImports,
287
+ fileReexports,
288
+ fileNodeIds,
289
+ barrelFiles,
290
+ rootDir,
291
+ ) as NativeEdge[];
292
+
293
+ for (const e of nativeEdges) {
294
+ allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
295
+ }
296
+ }
297
+
158
298
  // ── Call edges (native engine) ──────────────────────────────────────────
159
299
 
160
300
  function buildCallEdgesNative(
@@ -594,7 +734,28 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
594
734
  }
595
735
  }
596
736
 
597
- buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
737
+ // Skip native import-edge path for small incremental builds (≤3 files):
738
+ // napi-rs marshaling overhead exceeds computation savings.
739
+ const useNativeImportEdges =
740
+ native?.buildImportEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
741
+ if (useNativeImportEdges) {
742
+ const beforeLen = allEdgeRows.length;
743
+ buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native!);
744
+ // Fallback: if native produced 0 import edges but there are imports to
745
+ // process, the native binary may have a key-format mismatch (e.g. Windows
746
+ // path separators — #750). Retry with the JS implementation.
747
+ // NOTE: This also fires for codebases where every import targets an
748
+ // external package (npm deps) that the resolver intentionally skips.
749
+ // In that case the JS path resolves zero edges too, so the only cost
750
+ // is the redundant JS traversal — no correctness impact.
751
+ const hasImports = [...ctx.fileSymbols.values()].some((s) => s.imports.length > 0);
752
+ if (allEdgeRows.length === beforeLen && hasImports) {
753
+ debug('Native buildImportEdges produced 0 edges — falling back to JS');
754
+ buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
755
+ }
756
+ } else {
757
+ buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
758
+ }
598
759
 
599
760
  // Skip native call-edge path for small incremental builds (≤3 files):
600
761
  // napi-rs marshaling overhead for allNodes exceeds computation savings.