@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
@@ -21,13 +21,22 @@ function tryFastCollect(
21
21
  ctx: PipelineContext,
22
22
  ): { files: string[]; directories: Set<string> } | null {
23
23
  const { db, rootDir } = ctx;
24
+ const useNative = ctx.engineName === 'native' && !!ctx.nativeDb?.getCollectFilesData;
24
25
 
25
26
  // 1. Check that file_hashes table exists and has entries
26
27
  let dbFileCount: number;
27
- try {
28
- dbFileCount = (db.prepare('SELECT COUNT(*) as c FROM file_hashes').get() as { c: number }).c;
29
- } catch {
30
- return null;
28
+ let dbFiles: string[];
29
+ if (useNative) {
30
+ const data = ctx.nativeDb!.getCollectFilesData!();
31
+ dbFileCount = data.count;
32
+ dbFiles = data.files;
33
+ } else {
34
+ try {
35
+ dbFileCount = (db.prepare('SELECT COUNT(*) as c FROM file_hashes').get() as { c: number }).c;
36
+ } catch {
37
+ return null;
38
+ }
39
+ dbFiles = []; // deferred — loaded below only if needed
31
40
  }
32
41
  if (dbFileCount === 0) return null;
33
42
 
@@ -42,9 +51,11 @@ function tryFastCollect(
42
51
  if (!hasEntries) return null;
43
52
 
44
53
  // 3. Load existing file list from file_hashes (relative paths)
45
- const dbFiles = (db.prepare('SELECT file FROM file_hashes').all() as Array<{ file: string }>).map(
46
- (r) => r.file,
47
- );
54
+ if (!useNative) {
55
+ dbFiles = (db.prepare('SELECT file FROM file_hashes').all() as Array<{ file: string }>).map(
56
+ (r) => r.file,
57
+ );
58
+ }
48
59
 
49
60
  // 4. Apply journal deltas: remove deleted files, add new/changed files
50
61
  const fileSet = new Set(dbFiles);
@@ -60,14 +60,27 @@ function getChangedFiles(
60
60
  rootDir: string,
61
61
  nativeDb?: NativeDatabase,
62
62
  ): ChangeResult {
63
+ // Batched native path: single napi call for table check + all rows + max mtime
64
+ if (nativeDb?.getFileHashData) {
65
+ const data = nativeDb.getFileHashData();
66
+ if (!data.exists) {
67
+ return {
68
+ changed: allFiles.map((f) => ({ file: f })),
69
+ removed: [],
70
+ isFullBuild: true,
71
+ };
72
+ }
73
+ const existing = new Map<string, FileHashRow>(data.rows.map((r) => [r.file, r]));
74
+ const removed = detectRemovedFiles(existing, allFiles, rootDir);
75
+ const journalResult = tryJournalTier(db, existing, rootDir, removed, data.maxMtime);
76
+ if (journalResult) return journalResult;
77
+ return mtimeAndHashTiers(existing, allFiles, rootDir, removed);
78
+ }
79
+
80
+ // WASM / fallback path
63
81
  let hasTable = false;
64
82
  try {
65
- if (nativeDb) {
66
- nativeDb.queryGet('SELECT 1 FROM file_hashes LIMIT 1', []);
67
- } else {
68
- db.prepare('SELECT 1 FROM file_hashes LIMIT 1').get();
69
- }
70
- // Query succeeded → table exists (result may be undefined if table is empty)
83
+ db.prepare('SELECT 1 FROM file_hashes LIMIT 1').get();
71
84
  hasTable = true;
72
85
  } catch {
73
86
  /* table doesn't exist */
@@ -81,10 +94,7 @@ function getChangedFiles(
81
94
  };
82
95
  }
83
96
 
84
- const sql = 'SELECT file, hash, mtime, size FROM file_hashes';
85
- const rows = nativeDb
86
- ? (nativeDb.queryAll(sql, []) as unknown as FileHashRow[])
87
- : (db.prepare(sql).all() as FileHashRow[]);
97
+ const rows = db.prepare('SELECT file, hash, mtime, size FROM file_hashes').all() as FileHashRow[];
88
98
  const existing = new Map<string, FileHashRow>(rows.map((r) => [r.file, r]));
89
99
 
90
100
  const removed = detectRemovedFiles(existing, allFiles, rootDir);
@@ -116,14 +126,19 @@ function tryJournalTier(
116
126
  existing: Map<string, FileHashRow>,
117
127
  rootDir: string,
118
128
  removed: string[],
129
+ precomputedMaxMtime?: number,
119
130
  ): ChangeResult | null {
120
131
  const journal = readJournal(rootDir);
121
132
  if (!journal.valid) return null;
122
133
 
123
- const dbMtimes = db.prepare('SELECT MAX(mtime) as latest FROM file_hashes').get() as
124
- | { latest: number | null }
125
- | undefined;
126
- const latestDbMtime = dbMtimes?.latest || 0;
134
+ const latestDbMtime =
135
+ precomputedMaxMtime ??
136
+ ((
137
+ db.prepare('SELECT MAX(mtime) as latest FROM file_hashes').get() as
138
+ | { latest: number | null }
139
+ | undefined
140
+ )?.latest ||
141
+ 0);
127
142
  const hasJournalEntries = journal.changed!.length > 0 || journal.removed!.length > 0;
128
143
 
129
144
  if (!hasJournalEntries || journal.timestamp! < latestDbMtime) {
@@ -231,30 +246,42 @@ function mtimeAndHashTiers(
231
246
 
232
247
  async function runPendingAnalysis(ctx: PipelineContext): Promise<boolean> {
233
248
  const { db, opts, engineOpts, allFiles, rootDir } = ctx;
234
- const needsCfg =
235
- (opts as Record<string, unknown>).cfg !== false &&
236
- (() => {
237
- try {
238
- return (
239
- (db.prepare('SELECT COUNT(*) as c FROM cfg_blocks').get() as { c: number } | undefined)
240
- ?.c === 0
241
- );
242
- } catch {
243
- return true;
244
- }
245
- })();
246
- const needsDataflow =
247
- (opts as Record<string, unknown>).dataflow !== false &&
248
- (() => {
249
- try {
250
- return (
251
- (db.prepare('SELECT COUNT(*) as c FROM dataflow').get() as { c: number } | undefined)
252
- ?.c === 0
253
- );
254
- } catch {
255
- return true;
256
- }
257
- })();
249
+ const useNative = ctx.engineName === 'native' && !!ctx.nativeDb?.checkPendingAnalysis;
250
+
251
+ let needsCfg: boolean;
252
+ let needsDataflow: boolean;
253
+
254
+ if (useNative) {
255
+ const counts = ctx.nativeDb!.checkPendingAnalysis!();
256
+ needsCfg = (opts as Record<string, unknown>).cfg !== false && counts.cfgCount <= 0;
257
+ needsDataflow =
258
+ (opts as Record<string, unknown>).dataflow !== false && counts.dataflowCount <= 0;
259
+ } else {
260
+ needsCfg =
261
+ (opts as Record<string, unknown>).cfg !== false &&
262
+ (() => {
263
+ try {
264
+ return (
265
+ (db.prepare('SELECT COUNT(*) as c FROM cfg_blocks').get() as { c: number } | undefined)
266
+ ?.c === 0
267
+ );
268
+ } catch {
269
+ return true;
270
+ }
271
+ })();
272
+ needsDataflow =
273
+ (opts as Record<string, unknown>).dataflow !== false &&
274
+ (() => {
275
+ try {
276
+ return (
277
+ (db.prepare('SELECT COUNT(*) as c FROM dataflow').get() as { c: number } | undefined)
278
+ ?.c === 0
279
+ );
280
+ } catch {
281
+ return true;
282
+ }
283
+ })();
284
+ }
258
285
  if (!needsCfg && !needsDataflow) return false;
259
286
 
260
287
  info('No file changes. Running pending analysis pass...');
@@ -282,17 +309,27 @@ function healMetadata(ctx: PipelineContext): void {
282
309
  const { db, metadataUpdates } = ctx;
283
310
  if (!metadataUpdates || metadataUpdates.length === 0) return;
284
311
  try {
285
- const healHash = db.prepare(
286
- 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
287
- );
288
- const healTx = db.transaction(() => {
289
- for (const item of metadataUpdates) {
290
- const mtime = item.stat ? Math.floor(item.stat.mtime) : 0;
291
- const size = item.stat ? item.stat.size : 0;
292
- healHash.run(item.relPath, item.hash, mtime, size);
293
- }
294
- });
295
- healTx();
312
+ if (ctx.engineName === 'native' && ctx.nativeDb?.healFileMetadata) {
313
+ const entries = metadataUpdates.map((item) => ({
314
+ file: item.relPath,
315
+ hash: item.hash,
316
+ mtime: item.stat ? Math.floor(item.stat.mtime) : 0,
317
+ size: item.stat ? item.stat.size : 0,
318
+ }));
319
+ ctx.nativeDb.healFileMetadata(entries);
320
+ } else {
321
+ const healHash = db.prepare(
322
+ 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
323
+ );
324
+ const healTx = db.transaction(() => {
325
+ for (const item of metadataUpdates) {
326
+ const mtime = item.stat ? Math.floor(item.stat.mtime) : 0;
327
+ const size = item.stat ? item.stat.size : 0;
328
+ healHash.run(item.relPath, item.hash, mtime, size);
329
+ }
330
+ });
331
+ healTx();
332
+ }
296
333
  debug(`Self-healed mtime/size for ${metadataUpdates.length} files`);
297
334
  } catch {
298
335
  /* ignore heal errors */
@@ -303,9 +340,23 @@ function findReverseDependencies(
303
340
  db: BetterSqlite3Database,
304
341
  changedRelPaths: Set<string>,
305
342
  rootDir: string,
343
+ nativeDb?: NativeDatabase,
306
344
  ): Set<string> {
307
345
  const reverseDeps = new Set<string>();
308
346
  if (changedRelPaths.size === 0) return reverseDeps;
347
+
348
+ if (nativeDb?.findReverseDependencies) {
349
+ const changedArray = [...changedRelPaths];
350
+ const nativeResults = nativeDb.findReverseDependencies(changedArray);
351
+ for (const dep of nativeResults) {
352
+ const absPath = path.isAbsolute(dep) ? dep : path.join(rootDir, dep);
353
+ if (fs.existsSync(absPath)) {
354
+ reverseDeps.add(dep);
355
+ }
356
+ }
357
+ return reverseDeps;
358
+ }
359
+
309
360
  const findReverseDepsStmt = db.prepare(`
310
361
  SELECT DISTINCT n_src.file FROM edges e
311
362
  JOIN nodes n_src ON e.source_id = n_src.id
@@ -315,7 +366,7 @@ function findReverseDependencies(
315
366
  for (const relPath of changedRelPaths) {
316
367
  for (const row of findReverseDepsStmt.all(relPath) as Array<{ file: string }>) {
317
368
  if (!changedRelPaths.has(row.file) && !reverseDeps.has(row.file)) {
318
- const absPath = path.join(rootDir, row.file);
369
+ const absPath = path.isAbsolute(row.file) ? row.file : path.join(rootDir, row.file);
319
370
  if (fs.existsSync(absPath)) {
320
371
  reverseDeps.add(row.file);
321
372
  }
@@ -360,7 +411,10 @@ function purgeAndAddReverseDeps(
360
411
  }
361
412
  }
362
413
 
363
- function detectHasEmbeddings(db: BetterSqlite3Database): boolean {
414
+ function detectHasEmbeddings(db: BetterSqlite3Database, nativeDb?: NativeDatabase): boolean {
415
+ if (nativeDb?.hasEmbeddings) {
416
+ return nativeDb.hasEmbeddings();
417
+ }
364
418
  try {
365
419
  db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
366
420
  return true;
@@ -371,14 +425,14 @@ function detectHasEmbeddings(db: BetterSqlite3Database): boolean {
371
425
 
372
426
  function handleScopedBuild(ctx: PipelineContext): void {
373
427
  const { db, rootDir, opts } = ctx;
374
- ctx.hasEmbeddings = detectHasEmbeddings(db);
428
+ ctx.hasEmbeddings = detectHasEmbeddings(db, ctx.nativeDb);
375
429
  const changePaths = ctx.parseChanges.map(
376
430
  (item) => item.relPath || normalizePath(path.relative(rootDir, item.file)),
377
431
  );
378
432
  let reverseDeps = new Set<string>();
379
433
  if (!(opts as Record<string, unknown>).noReverseDeps) {
380
434
  const changedRelPaths = new Set<string>([...changePaths, ...ctx.removed]);
381
- reverseDeps = findReverseDependencies(db, changedRelPaths, rootDir);
435
+ reverseDeps = findReverseDependencies(db, changedRelPaths, rootDir, ctx.nativeDb);
382
436
  }
383
437
  purgeAndAddReverseDeps(ctx, changePaths, reverseDeps);
384
438
  info(
@@ -388,7 +442,7 @@ function handleScopedBuild(ctx: PipelineContext): void {
388
442
 
389
443
  function handleFullBuild(ctx: PipelineContext): void {
390
444
  const { db } = ctx;
391
- const hasEmbeddings = detectHasEmbeddings(db);
445
+ const hasEmbeddings = detectHasEmbeddings(db, ctx.nativeDb);
392
446
  ctx.hasEmbeddings = hasEmbeddings;
393
447
  const deletions =
394
448
  'PRAGMA foreign_keys = OFF; DELETE FROM cfg_edges; DELETE FROM cfg_blocks; DELETE FROM node_metrics; DELETE FROM edges; DELETE FROM function_complexity; DELETE FROM dataflow; DELETE FROM ast_nodes; DELETE FROM nodes; PRAGMA foreign_keys = ON;';
@@ -401,7 +455,7 @@ function handleFullBuild(ctx: PipelineContext): void {
401
455
 
402
456
  function handleIncrementalBuild(ctx: PipelineContext): void {
403
457
  const { db, rootDir, opts } = ctx;
404
- ctx.hasEmbeddings = detectHasEmbeddings(db);
458
+ ctx.hasEmbeddings = detectHasEmbeddings(db, ctx.nativeDb);
405
459
  let reverseDeps = new Set<string>();
406
460
  if (!(opts as Record<string, unknown>).noReverseDeps) {
407
461
  const changedRelPaths = new Set<string>();
@@ -411,7 +465,7 @@ function handleIncrementalBuild(ctx: PipelineContext): void {
411
465
  for (const relPath of ctx.removed) {
412
466
  changedRelPaths.add(relPath);
413
467
  }
414
- reverseDeps = findReverseDependencies(db, changedRelPaths, rootDir);
468
+ reverseDeps = findReverseDependencies(db, changedRelPaths, rootDir, ctx.nativeDb);
415
469
  }
416
470
  info(
417
471
  `Incremental: ${ctx.parseChanges.length} changed, ${ctx.removed.length} removed${reverseDeps.size > 0 ? `, ${reverseDeps.size} reverse-deps` : ''}`,
@@ -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(),
@@ -114,11 +114,33 @@ function persistBuildMetadata(
114
114
  * Run advisory checks on full builds: orphaned embeddings, stale embeddings,
115
115
  * and unused exports. Informational only — does not affect correctness.
116
116
  */
117
- function runAdvisoryChecks(
118
- db: PipelineContext['db'],
119
- hasEmbeddings: boolean,
120
- buildNow: Date,
121
- ): void {
117
+ function runAdvisoryChecks(ctx: PipelineContext, hasEmbeddings: boolean, buildNow: Date): void {
118
+ // Batched native path: single napi call for all 3 advisory checks
119
+ if (ctx.engineName === 'native' && ctx.nativeDb?.runAdvisoryChecks) {
120
+ const result = ctx.nativeDb.runAdvisoryChecks(hasEmbeddings);
121
+ if (result.orphanedEmbeddings > 0) {
122
+ warn(
123
+ `${result.orphanedEmbeddings} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
124
+ );
125
+ }
126
+ if (result.embedBuiltAt) {
127
+ const embedTime = new Date(result.embedBuiltAt).getTime();
128
+ if (!Number.isNaN(embedTime) && embedTime < buildNow.getTime()) {
129
+ warn(
130
+ 'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
131
+ );
132
+ }
133
+ }
134
+ if (result.unusedExports > 0) {
135
+ warn(
136
+ `${result.unusedExports} exported symbol${result.unusedExports > 1 ? 's have' : ' has'} zero cross-file consumers. Run "codegraph exports <file> --unused" to inspect.`,
137
+ );
138
+ }
139
+ return;
140
+ }
141
+
142
+ const { db } = ctx;
143
+
122
144
  // Orphaned embeddings warning
123
145
  if (hasEmbeddings) {
124
146
  try {
@@ -197,9 +219,17 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
197
219
  // both the stale-embeddings comparison and the persisted built_at metadata.
198
220
  const buildNow = new Date();
199
221
 
200
- const nodeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
201
- const actualEdgeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number })
202
- .c;
222
+ const useNative = ctx.engineName === 'native' && !!ctx.nativeDb?.getFinalizeCounts;
223
+ let nodeCount: number;
224
+ let actualEdgeCount: number;
225
+ if (useNative) {
226
+ const counts = ctx.nativeDb!.getFinalizeCounts!();
227
+ nodeCount = counts.nodeCount;
228
+ actualEdgeCount = counts.edgeCount;
229
+ } else {
230
+ nodeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
231
+ actualEdgeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number }).c;
232
+ }
203
233
  info(`Graph built: ${nodeCount} nodes, ${actualEdgeCount} edges`);
204
234
  info(`Stored in ${ctx.dbPath}`);
205
235
 
@@ -213,7 +243,7 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
213
243
  'Finalize: skipping advisory queries (orphaned/stale embeddings, unused exports) for incremental build',
214
244
  );
215
245
  } else {
216
- runAdvisoryChecks(ctx.db, hasEmbeddings, buildNow);
246
+ runAdvisoryChecks(ctx, hasEmbeddings, buildNow);
217
247
  }
218
248
 
219
249
  // Intentionally measured before closeDb / writeJournalHeader / auto-registration:
@@ -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,41 +38,31 @@ interface PrecomputedFileData {
36
38
  _reverseDepOnly?: boolean;
37
39
  }
38
40
 
39
- // ── Native fast-path ─────────────────────────────────────────────────
40
-
41
- function tryNativeInsert(ctx: PipelineContext): boolean {
42
- // Disabled: bulkInsertNodes corrupts the DB when both the JS (better-sqlite3)
43
- // and Rust (rusqlite) connections are open to the same WAL-mode file.
44
- // The native path was never operational before — it always crashed on null
45
- // visibility serialisation. See #696 for the dual-connection fix.
46
- if (ctx.db) return false;
47
-
48
- // Use NativeDatabase persistent connection (Phase 6.15+).
49
- // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
50
- if (!ctx.nativeDb?.bulkInsertNodes) return false;
51
-
52
- const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
41
+ // ── Native fast-path helpers ─────────────────────────────────────────
53
42
 
54
- // Marshal allSymbols InsertNodesBatch[]
55
- const batches: Array<{
56
- file: string;
57
- 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<{
58
53
  name: string;
59
54
  kind: string;
60
55
  line: number;
61
56
  endLine?: number;
62
57
  visibility?: string;
63
- children: Array<{
64
- name: string;
65
- kind: string;
66
- line: number;
67
- endLine?: number;
68
- visibility?: string;
69
- }>;
70
58
  }>;
71
- exports: Array<{ name: string; kind: string; line: number }>;
72
- }> = [];
59
+ }>;
60
+ exports: Array<{ name: string; kind: string; line: number }>;
61
+ }
73
62
 
63
+ /** Marshal allSymbols into the batch format expected by native bulkInsertNodes. */
64
+ function marshalSymbolBatches(allSymbols: Map<string, ExtractorOutput>): InsertNodesBatch[] {
65
+ const batches: InsertNodesBatch[] = [];
74
66
  for (const [relPath, symbols] of allSymbols) {
75
67
  batches.push({
76
68
  file: relPath,
@@ -95,14 +87,18 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
95
87
  })),
96
88
  });
97
89
  }
90
+ return batches;
91
+ }
98
92
 
99
- // Build file hash entries
100
- const precomputedData = new Map<string, PrecomputedFileData>();
101
- for (const item of filesToParse) {
102
- if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
103
- }
104
-
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 }> {
105
100
  const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
101
+
106
102
  for (const [relPath] of allSymbols) {
107
103
  const precomputed = precomputedData.get(relPath);
108
104
  if (precomputed?._reverseDepOnly) {
@@ -125,7 +121,8 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
125
121
  let code: string | null;
126
122
  try {
127
123
  code = readFileSafe(absPath);
128
- } catch {
124
+ } catch (e) {
125
+ debug(`buildFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
129
126
  code = null;
130
127
  }
131
128
  if (code !== null) {
@@ -144,7 +141,44 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
144
141
  fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
145
142
  }
146
143
 
147
- return ctx.nativeDb!.bulkInsertNodes(batches, fileHashes, removed);
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
+
162
+ // WAL guard: same suspendJsDb/resumeJsDb pattern used by feature modules
163
+ // (ast, cfg, complexity, dataflow). Checkpoint JS side before native write,
164
+ // then checkpoint native side after, so neither library reads WAL frames
165
+ // written by the other (#696, #709, #715, #717).
166
+ let result: boolean;
167
+ try {
168
+ if (ctx.db) {
169
+ ctx.db.pragma('wal_checkpoint(TRUNCATE)');
170
+ }
171
+ result = ctx.nativeDb!.bulkInsertNodes(batches, fileHashes, removed);
172
+ } finally {
173
+ try {
174
+ ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
175
+ } catch (e) {
176
+ debug(
177
+ `tryNativeInsert: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
178
+ );
179
+ }
180
+ }
181
+ return result;
148
182
  }
149
183
 
150
184
  // ── JS fallback: Phase 1 ────────────────────────────────────────────
@@ -313,7 +347,8 @@ function updateFileHashes(
313
347
  let code: string | null;
314
348
  try {
315
349
  code = readFileSafe(absPath);
316
- } catch {
350
+ } catch (e) {
351
+ debug(`updateFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
317
352
  code = null;
318
353
  }
319
354
  if (code !== null) {
@@ -353,8 +388,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
353
388
  // Removed-file hash cleanup is handled inside the native call
354
389
  return;
355
390
  }
356
- } catch {
357
- // Native insert failed fall through to JS implementation
391
+ } catch (e) {
392
+ debug(`insertNodes: native insert failed, falling back to JS: ${toErrorMessage(e)}`);
358
393
  }
359
394
  }
360
395
 
@@ -369,7 +404,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
369
404
  upsertHash = ctx.db.prepare(
370
405
  'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
371
406
  );
372
- } catch {
407
+ } catch (e) {
408
+ debug(`insertNodes: file_hashes prepare failed (table may not exist): ${toErrorMessage(e)}`);
373
409
  upsertHash = null;
374
410
  }
375
411