@optave/codegraph 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/README.md +47 -21
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +206 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/dart.d.ts +6 -0
  103. package/dist/extractors/dart.d.ts.map +1 -0
  104. package/dist/extractors/dart.js +277 -0
  105. package/dist/extractors/dart.js.map +1 -0
  106. package/dist/extractors/elixir.d.ts +9 -0
  107. package/dist/extractors/elixir.d.ts.map +1 -0
  108. package/dist/extractors/elixir.js +223 -0
  109. package/dist/extractors/elixir.js.map +1 -0
  110. package/dist/extractors/go.d.ts.map +1 -1
  111. package/dist/extractors/go.js +126 -130
  112. package/dist/extractors/go.js.map +1 -1
  113. package/dist/extractors/haskell.d.ts +8 -0
  114. package/dist/extractors/haskell.d.ts.map +1 -0
  115. package/dist/extractors/haskell.js +217 -0
  116. package/dist/extractors/haskell.js.map +1 -0
  117. package/dist/extractors/hcl.js +6 -6
  118. package/dist/extractors/hcl.js.map +1 -1
  119. package/dist/extractors/helpers.d.ts +32 -1
  120. package/dist/extractors/helpers.d.ts.map +1 -1
  121. package/dist/extractors/helpers.js +74 -0
  122. package/dist/extractors/helpers.js.map +1 -1
  123. package/dist/extractors/index.d.ts +12 -0
  124. package/dist/extractors/index.d.ts.map +1 -1
  125. package/dist/extractors/index.js +12 -0
  126. package/dist/extractors/index.js.map +1 -1
  127. package/dist/extractors/java.d.ts.map +1 -1
  128. package/dist/extractors/java.js +32 -47
  129. package/dist/extractors/java.js.map +1 -1
  130. package/dist/extractors/javascript.d.ts.map +1 -1
  131. package/dist/extractors/javascript.js +306 -292
  132. package/dist/extractors/javascript.js.map +1 -1
  133. package/dist/extractors/kotlin.d.ts +6 -0
  134. package/dist/extractors/kotlin.d.ts.map +1 -0
  135. package/dist/extractors/kotlin.js +275 -0
  136. package/dist/extractors/kotlin.js.map +1 -0
  137. package/dist/extractors/lua.d.ts +6 -0
  138. package/dist/extractors/lua.d.ts.map +1 -0
  139. package/dist/extractors/lua.js +162 -0
  140. package/dist/extractors/lua.js.map +1 -0
  141. package/dist/extractors/ocaml.d.ts +6 -0
  142. package/dist/extractors/ocaml.d.ts.map +1 -0
  143. package/dist/extractors/ocaml.js +236 -0
  144. package/dist/extractors/ocaml.js.map +1 -0
  145. package/dist/extractors/php.d.ts.map +1 -1
  146. package/dist/extractors/php.js +39 -44
  147. package/dist/extractors/php.js.map +1 -1
  148. package/dist/extractors/python.d.ts.map +1 -1
  149. package/dist/extractors/python.js +75 -93
  150. package/dist/extractors/python.js.map +1 -1
  151. package/dist/extractors/ruby.js +6 -13
  152. package/dist/extractors/ruby.js.map +1 -1
  153. package/dist/extractors/rust.d.ts.map +1 -1
  154. package/dist/extractors/rust.js +58 -83
  155. package/dist/extractors/rust.js.map +1 -1
  156. package/dist/extractors/scala.d.ts +6 -0
  157. package/dist/extractors/scala.d.ts.map +1 -0
  158. package/dist/extractors/scala.js +269 -0
  159. package/dist/extractors/scala.js.map +1 -0
  160. package/dist/extractors/swift.d.ts +6 -0
  161. package/dist/extractors/swift.d.ts.map +1 -0
  162. package/dist/extractors/swift.js +275 -0
  163. package/dist/extractors/swift.js.map +1 -0
  164. package/dist/extractors/zig.d.ts +9 -0
  165. package/dist/extractors/zig.d.ts.map +1 -0
  166. package/dist/extractors/zig.js +276 -0
  167. package/dist/extractors/zig.js.map +1 -0
  168. package/dist/features/ast.d.ts +2 -0
  169. package/dist/features/ast.d.ts.map +1 -1
  170. package/dist/features/ast.js +9 -24
  171. package/dist/features/ast.js.map +1 -1
  172. package/dist/features/audit.d.ts.map +1 -1
  173. package/dist/features/audit.js +17 -21
  174. package/dist/features/audit.js.map +1 -1
  175. package/dist/features/branch-compare.d.ts.map +1 -1
  176. package/dist/features/branch-compare.js +47 -3
  177. package/dist/features/branch-compare.js.map +1 -1
  178. package/dist/features/cfg.d.ts +7 -1
  179. package/dist/features/cfg.d.ts.map +1 -1
  180. package/dist/features/cfg.js +72 -61
  181. package/dist/features/cfg.js.map +1 -1
  182. package/dist/features/check.d.ts.map +1 -1
  183. package/dist/features/check.js +79 -62
  184. package/dist/features/check.js.map +1 -1
  185. package/dist/features/complexity-query.d.ts.map +1 -1
  186. package/dist/features/complexity-query.js +142 -137
  187. package/dist/features/complexity-query.js.map +1 -1
  188. package/dist/features/complexity.d.ts +7 -1
  189. package/dist/features/complexity.d.ts.map +1 -1
  190. package/dist/features/complexity.js +62 -1
  191. package/dist/features/complexity.js.map +1 -1
  192. package/dist/features/dataflow.d.ts +7 -1
  193. package/dist/features/dataflow.d.ts.map +1 -1
  194. package/dist/features/dataflow.js +356 -188
  195. package/dist/features/dataflow.js.map +1 -1
  196. package/dist/features/graph-enrichment.d.ts.map +1 -1
  197. package/dist/features/graph-enrichment.js +117 -104
  198. package/dist/features/graph-enrichment.js.map +1 -1
  199. package/dist/features/sequence.d.ts.map +1 -1
  200. package/dist/features/sequence.js +25 -4
  201. package/dist/features/sequence.js.map +1 -1
  202. package/dist/features/structure-query.d.ts.map +1 -1
  203. package/dist/features/structure-query.js +29 -4
  204. package/dist/features/structure-query.js.map +1 -1
  205. package/dist/features/structure.d.ts.map +1 -1
  206. package/dist/features/structure.js +35 -15
  207. package/dist/features/structure.js.map +1 -1
  208. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  209. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  210. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  211. package/dist/graph/algorithms/leiden/index.js +43 -28
  212. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  213. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  214. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  215. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  216. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  217. package/dist/graph/algorithms/leiden/partition.js +89 -106
  218. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  219. package/dist/graph/model.d.ts +2 -0
  220. package/dist/graph/model.d.ts.map +1 -1
  221. package/dist/graph/model.js +20 -8
  222. package/dist/graph/model.js.map +1 -1
  223. package/dist/infrastructure/config.d.ts +0 -8
  224. package/dist/infrastructure/config.d.ts.map +1 -1
  225. package/dist/infrastructure/config.js +73 -62
  226. package/dist/infrastructure/config.js.map +1 -1
  227. package/dist/infrastructure/registry.d.ts +0 -8
  228. package/dist/infrastructure/registry.d.ts.map +1 -1
  229. package/dist/infrastructure/registry.js +12 -14
  230. package/dist/infrastructure/registry.js.map +1 -1
  231. package/dist/mcp/server.d.ts.map +1 -1
  232. package/dist/mcp/server.js +45 -36
  233. package/dist/mcp/server.js.map +1 -1
  234. package/dist/presentation/audit.d.ts.map +1 -1
  235. package/dist/presentation/audit.js +61 -57
  236. package/dist/presentation/audit.js.map +1 -1
  237. package/dist/presentation/branch-compare.d.ts.map +1 -1
  238. package/dist/presentation/branch-compare.js +56 -38
  239. package/dist/presentation/branch-compare.js.map +1 -1
  240. package/dist/presentation/check.d.ts.map +1 -1
  241. package/dist/presentation/check.js +30 -32
  242. package/dist/presentation/check.js.map +1 -1
  243. package/dist/presentation/colors.d.ts.map +1 -1
  244. package/dist/presentation/colors.js +2 -0
  245. package/dist/presentation/colors.js.map +1 -1
  246. package/dist/presentation/complexity.d.ts.map +1 -1
  247. package/dist/presentation/complexity.js +25 -19
  248. package/dist/presentation/complexity.js.map +1 -1
  249. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  250. package/dist/presentation/queries-cli/exports.js +15 -15
  251. package/dist/presentation/queries-cli/exports.js.map +1 -1
  252. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  253. package/dist/presentation/queries-cli/impact.js +29 -19
  254. package/dist/presentation/queries-cli/impact.js.map +1 -1
  255. package/dist/types.d.ts +182 -7
  256. package/dist/types.d.ts.map +1 -1
  257. package/grammars/tree-sitter-bash.wasm +0 -0
  258. package/grammars/tree-sitter-c.wasm +0 -0
  259. package/grammars/tree-sitter-cpp.wasm +0 -0
  260. package/grammars/tree-sitter-dart.wasm +0 -0
  261. package/grammars/tree-sitter-elixir.wasm +0 -0
  262. package/grammars/tree-sitter-haskell.wasm +0 -0
  263. package/grammars/tree-sitter-kotlin.wasm +0 -0
  264. package/grammars/tree-sitter-lua.wasm +0 -0
  265. package/grammars/tree-sitter-ocaml.wasm +0 -0
  266. package/grammars/tree-sitter-scala.wasm +0 -0
  267. package/grammars/tree-sitter-swift.wasm +0 -0
  268. package/grammars/tree-sitter-zig.wasm +0 -0
  269. package/package.json +19 -7
  270. package/src/ast-analysis/engine.ts +147 -138
  271. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  272. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  273. package/src/db/connection.ts +90 -59
  274. package/src/db/index.ts +1 -0
  275. package/src/db/migrations.ts +36 -32
  276. package/src/domain/analysis/context.ts +73 -75
  277. package/src/domain/analysis/dependencies.ts +78 -68
  278. package/src/domain/analysis/exports.ts +45 -34
  279. package/src/domain/analysis/fn-impact.ts +67 -64
  280. package/src/domain/analysis/module-map.ts +103 -8
  281. package/src/domain/analysis/query-helpers.ts +35 -0
  282. package/src/domain/graph/builder/helpers.ts +12 -6
  283. package/src/domain/graph/builder/incremental.ts +3 -2
  284. package/src/domain/graph/builder/pipeline.ts +71 -3
  285. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  286. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  287. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  288. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  289. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  290. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  291. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  292. package/src/domain/graph/resolve.ts +34 -46
  293. package/src/domain/graph/watcher.ts +12 -14
  294. package/src/domain/parser.ts +222 -97
  295. package/src/domain/search/search/cli-formatter.ts +121 -94
  296. package/src/extractors/bash.ts +97 -0
  297. package/src/extractors/c.ts +212 -0
  298. package/src/extractors/cpp.ts +298 -0
  299. package/src/extractors/csharp.ts +53 -56
  300. package/src/extractors/dart.ts +304 -0
  301. package/src/extractors/elixir.ts +251 -0
  302. package/src/extractors/go.ts +152 -134
  303. package/src/extractors/haskell.ts +235 -0
  304. package/src/extractors/hcl.ts +6 -6
  305. package/src/extractors/helpers.ts +93 -1
  306. package/src/extractors/index.ts +12 -0
  307. package/src/extractors/java.ts +43 -48
  308. package/src/extractors/javascript.ts +328 -281
  309. package/src/extractors/kotlin.ts +293 -0
  310. package/src/extractors/lua.ts +169 -0
  311. package/src/extractors/ocaml.ts +259 -0
  312. package/src/extractors/php.ts +46 -40
  313. package/src/extractors/python.ts +81 -104
  314. package/src/extractors/ruby.ts +6 -13
  315. package/src/extractors/rust.ts +65 -85
  316. package/src/extractors/scala.ts +285 -0
  317. package/src/extractors/swift.ts +293 -0
  318. package/src/extractors/zig.ts +294 -0
  319. package/src/features/ast.ts +10 -25
  320. package/src/features/audit.ts +24 -20
  321. package/src/features/branch-compare.ts +51 -4
  322. package/src/features/cfg.ts +113 -65
  323. package/src/features/check.ts +90 -74
  324. package/src/features/complexity-query.ts +181 -163
  325. package/src/features/complexity.ts +64 -1
  326. package/src/features/dataflow.ts +462 -217
  327. package/src/features/graph-enrichment.ts +161 -117
  328. package/src/features/sequence.ts +27 -4
  329. package/src/features/structure-query.ts +43 -4
  330. package/src/features/structure.ts +50 -22
  331. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  332. package/src/graph/algorithms/leiden/index.ts +67 -28
  333. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  334. package/src/graph/algorithms/leiden/partition.ts +131 -98
  335. package/src/graph/model.ts +19 -7
  336. package/src/infrastructure/config.ts +60 -58
  337. package/src/infrastructure/registry.ts +17 -14
  338. package/src/mcp/server.ts +46 -37
  339. package/src/presentation/audit.ts +72 -67
  340. package/src/presentation/branch-compare.ts +54 -50
  341. package/src/presentation/check.ts +34 -34
  342. package/src/presentation/colors.ts +2 -0
  343. package/src/presentation/complexity.ts +39 -33
  344. package/src/presentation/queries-cli/exports.ts +17 -17
  345. package/src/presentation/queries-cli/impact.ts +30 -22
  346. package/src/types.ts +195 -7
@@ -17,12 +17,8 @@ import { CODEGRAPH_VERSION } from '../../../../shared/version.js';
17
17
  import { writeJournalHeader } from '../../journal.js';
18
18
  import type { PipelineContext } from '../context.js';
19
19
 
20
- export async function finalize(ctx: PipelineContext): Promise<void> {
21
- const { db, allSymbols, rootDir, isFullBuild, hasEmbeddings, config, opts, schemaVersion } = ctx;
22
-
23
- const t0 = performance.now();
24
-
25
- // Release cached WASM trees
20
+ /** Release cached WASM parse trees to free memory. */
21
+ function releaseWasmTrees(allSymbols: PipelineContext['allSymbols']): void {
26
22
  for (const [, symbols] of allSymbols) {
27
23
  const tree = symbols._tree as { delete?: () => void } | undefined;
28
24
  if (tree && typeof tree.delete === 'function') {
@@ -35,133 +31,141 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
35
31
  symbols._tree = undefined;
36
32
  symbols._langId = undefined;
37
33
  }
34
+ }
38
35
 
39
- // Capture a single wall-clock timestamp for the current build — used for
40
- // both the stale-embeddings comparison and the persisted built_at metadata.
41
- const buildNow = new Date();
36
+ /**
37
+ * Detect significant drift between current and previous node/edge counts.
38
+ * Skipped for small incremental changes where count fluctuation is expected.
39
+ */
40
+ function detectIncrementalDrift(
41
+ ctx: PipelineContext,
42
+ nodeCount: number,
43
+ actualEdgeCount: number,
44
+ ): void {
45
+ const { db, allSymbols, config } = ctx;
46
+ const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
47
+ if (ctx.isFullBuild || allSymbols.size <= 3) return;
42
48
 
43
- const nodeCount = (db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
44
- const actualEdgeCount = (db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number }).c;
45
- info(`Graph built: ${nodeCount} nodes, ${actualEdgeCount} edges`);
46
- info(`Stored in ${ctx.dbPath}`);
49
+ const prevNodes = useNativeDb
50
+ ? ctx.nativeDb!.getBuildMeta('node_count')
51
+ : getBuildMeta(db, 'node_count');
52
+ const prevEdges = useNativeDb
53
+ ? ctx.nativeDb!.getBuildMeta('edge_count')
54
+ : getBuildMeta(db, 'edge_count');
55
+ if (!prevNodes || !prevEdges) return;
47
56
 
48
- // Incremental drift detection — skip for small incremental changes where
49
- // count fluctuation is expected (reverse-dep edge churn).
50
- if (!isFullBuild && allSymbols.size > 3) {
51
- const prevNodes = ctx.nativeDb
52
- ? ctx.nativeDb.getBuildMeta('node_count')
53
- : getBuildMeta(db, 'node_count');
54
- const prevEdges = ctx.nativeDb
55
- ? ctx.nativeDb.getBuildMeta('edge_count')
56
- : getBuildMeta(db, 'edge_count');
57
- if (prevNodes && prevEdges) {
58
- const prevN = Number(prevNodes);
59
- const prevE = Number(prevEdges);
60
- if (prevN > 0) {
61
- const nodeDrift = Math.abs(nodeCount - prevN) / prevN;
62
- const edgeDrift = prevE > 0 ? Math.abs(actualEdgeCount - prevE) / prevE : 0;
63
- const driftThreshold =
64
- (config as { build?: { driftThreshold?: number } }).build?.driftThreshold ?? 0.2;
65
- if (nodeDrift > driftThreshold || edgeDrift > driftThreshold) {
66
- warn(
67
- `Incremental build diverged significantly from previous counts (nodes: ${prevN}\u2192${nodeCount} [${(nodeDrift * 100).toFixed(1)}%], edges: ${prevE}\u2192${actualEdgeCount} [${(edgeDrift * 100).toFixed(1)}%], threshold: ${(driftThreshold * 100).toFixed(0)}%). Consider rebuilding with --no-incremental.`,
68
- );
69
- }
70
- }
71
- }
57
+ const prevN = Number(prevNodes);
58
+ const prevE = Number(prevEdges);
59
+ if (prevN <= 0) return;
60
+
61
+ const nodeDrift = Math.abs(nodeCount - prevN) / prevN;
62
+ const edgeDrift = prevE > 0 ? Math.abs(actualEdgeCount - prevE) / prevE : 0;
63
+ const driftThreshold =
64
+ (config as { build?: { driftThreshold?: number } }).build?.driftThreshold ?? 0.2;
65
+ if (nodeDrift > driftThreshold || edgeDrift > driftThreshold) {
66
+ warn(
67
+ `Incremental build diverged significantly from previous counts (nodes: ${prevN}\u2192${nodeCount} [${(nodeDrift * 100).toFixed(1)}%], edges: ${prevE}\u2192${actualEdgeCount} [${(edgeDrift * 100).toFixed(1)}%], threshold: ${(driftThreshold * 100).toFixed(0)}%). Consider rebuilding with --no-incremental.`,
68
+ );
72
69
  }
70
+ }
73
71
 
74
- // For small incremental builds, skip persisting build metadata — the
75
- // engine/version/schema haven't changed (would have triggered a full rebuild),
76
- // built_at is only used by stale-embeddings check (skipped for incremental),
77
- // and counts are only used by drift detection (skipped for ≤3 files).
78
- // This avoids a transaction commit + WAL fsync (~15-30ms).
79
- // Threshold aligned with drift detection gate (allSymbols.size > 3) so stored
80
- // counts stay fresh whenever drift detection reads them.
81
- if (isFullBuild || allSymbols.size > 3) {
82
- try {
83
- if (ctx.nativeDb) {
84
- ctx.nativeDb.setBuildMeta(
85
- Object.entries({
86
- engine: ctx.engineName,
87
- engine_version: ctx.engineVersion || '',
88
- codegraph_version: CODEGRAPH_VERSION,
89
- schema_version: String(schemaVersion),
90
- built_at: buildNow.toISOString(),
91
- node_count: String(nodeCount),
92
- edge_count: String(actualEdgeCount),
93
- }).map(([key, value]) => ({ key, value: String(value) })),
94
- );
95
- } else {
96
- setBuildMeta(db, {
72
+ /**
73
+ * Persist build metadata (engine, version, counts, timestamp).
74
+ * Skipped for small incremental builds to avoid WAL fsync cost.
75
+ */
76
+ function persistBuildMetadata(
77
+ ctx: PipelineContext,
78
+ nodeCount: number,
79
+ actualEdgeCount: number,
80
+ buildNow: Date,
81
+ ): void {
82
+ const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
83
+ if (!ctx.isFullBuild && ctx.allSymbols.size <= 3) return;
84
+ try {
85
+ if (useNativeDb) {
86
+ ctx.nativeDb!.setBuildMeta(
87
+ Object.entries({
97
88
  engine: ctx.engineName,
98
89
  engine_version: ctx.engineVersion || '',
99
90
  codegraph_version: CODEGRAPH_VERSION,
100
- schema_version: String(schemaVersion),
91
+ schema_version: String(ctx.schemaVersion),
101
92
  built_at: buildNow.toISOString(),
102
- node_count: nodeCount,
103
- edge_count: actualEdgeCount,
104
- });
105
- }
106
- } catch (err) {
107
- warn(`Failed to write build metadata: ${(err as Error).message}`);
93
+ node_count: String(nodeCount),
94
+ edge_count: String(actualEdgeCount),
95
+ }).map(([key, value]) => ({ key, value: String(value) })),
96
+ );
97
+ } else {
98
+ setBuildMeta(ctx.db, {
99
+ engine: ctx.engineName,
100
+ engine_version: ctx.engineVersion || '',
101
+ codegraph_version: CODEGRAPH_VERSION,
102
+ schema_version: String(ctx.schemaVersion),
103
+ built_at: buildNow.toISOString(),
104
+ node_count: nodeCount,
105
+ edge_count: actualEdgeCount,
106
+ });
108
107
  }
108
+ } catch (err) {
109
+ warn(`Failed to write build metadata: ${(err as Error).message}`);
109
110
  }
111
+ }
110
112
 
111
- // Skip expensive advisory queries for incremental builds — these are
112
- // informational warnings that don't affect correctness and cost ~40-60ms.
113
- if (!isFullBuild) {
114
- debug(
115
- 'Finalize: skipping advisory queries (orphaned/stale embeddings, unused exports) for incremental build',
116
- );
117
- } else {
118
- // Orphaned embeddings warning
119
- if (hasEmbeddings) {
120
- try {
121
- const orphaned = (
122
- db
123
- .prepare(
124
- 'SELECT COUNT(*) as c FROM embeddings WHERE node_id NOT IN (SELECT id FROM nodes)',
125
- )
126
- .get() as { c: number }
127
- ).c;
128
- if (orphaned > 0) {
129
- warn(
130
- `${orphaned} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
131
- );
132
- }
133
- } catch {
134
- /* ignore - embeddings table may have been dropped */
113
+ /**
114
+ * Run advisory checks on full builds: orphaned embeddings, stale embeddings,
115
+ * and unused exports. Informational only — does not affect correctness.
116
+ */
117
+ function runAdvisoryChecks(
118
+ db: PipelineContext['db'],
119
+ hasEmbeddings: boolean,
120
+ buildNow: Date,
121
+ ): void {
122
+ // Orphaned embeddings warning
123
+ if (hasEmbeddings) {
124
+ try {
125
+ const orphaned = (
126
+ db
127
+ .prepare(
128
+ 'SELECT COUNT(*) as c FROM embeddings WHERE node_id NOT IN (SELECT id FROM nodes)',
129
+ )
130
+ .get() as { c: number }
131
+ ).c;
132
+ if (orphaned > 0) {
133
+ warn(
134
+ `${orphaned} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
135
+ );
135
136
  }
137
+ } catch {
138
+ /* ignore - embeddings table may have been dropped */
136
139
  }
140
+ }
137
141
 
138
- // Stale embeddings warning (built before current graph rebuild)
139
- if (hasEmbeddings) {
140
- try {
141
- const embedBuiltAt = (
142
- db.prepare("SELECT value FROM embedding_meta WHERE key = 'built_at'").get() as
143
- | { value: string }
144
- | undefined
145
- )?.value;
146
- if (embedBuiltAt) {
147
- const embedTime = new Date(embedBuiltAt).getTime();
148
- if (!Number.isNaN(embedTime) && embedTime < buildNow.getTime()) {
149
- warn(
150
- 'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
151
- );
152
- }
142
+ // Stale embeddings warning (built before current graph rebuild)
143
+ if (hasEmbeddings) {
144
+ try {
145
+ const embedBuiltAt = (
146
+ db.prepare("SELECT value FROM embedding_meta WHERE key = 'built_at'").get() as
147
+ | { value: string }
148
+ | undefined
149
+ )?.value;
150
+ if (embedBuiltAt) {
151
+ const embedTime = new Date(embedBuiltAt).getTime();
152
+ if (!Number.isNaN(embedTime) && embedTime < buildNow.getTime()) {
153
+ warn(
154
+ 'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
155
+ );
153
156
  }
154
- } catch {
155
- /* ignore - embedding_meta table may not exist */
156
157
  }
158
+ } catch {
159
+ /* ignore - embedding_meta table may not exist */
157
160
  }
161
+ }
158
162
 
159
- // Unused exports warning
160
- try {
161
- const unusedCount = (
162
- db
163
- .prepare(
164
- `SELECT COUNT(*) as c FROM nodes
163
+ // Unused exports warning
164
+ try {
165
+ const unusedCount = (
166
+ db
167
+ .prepare(
168
+ `SELECT COUNT(*) as c FROM nodes
165
169
  WHERE exported = 1 AND kind != 'file'
166
170
  AND id NOT IN (
167
171
  SELECT DISTINCT e.target_id FROM edges e
@@ -169,17 +173,47 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
169
173
  JOIN nodes target ON e.target_id = target.id
170
174
  WHERE e.kind = 'calls' AND caller.file != target.file
171
175
  )`,
172
- )
173
- .get() as { c: number }
174
- ).c;
175
- if (unusedCount > 0) {
176
- warn(
177
- `${unusedCount} exported symbol${unusedCount > 1 ? 's have' : ' has'} zero cross-file consumers. Run "codegraph exports <file> --unused" to inspect.`,
178
- );
179
- }
180
- } catch {
181
- /* exported column may not exist on older DBs */
176
+ )
177
+ .get() as { c: number }
178
+ ).c;
179
+ if (unusedCount > 0) {
180
+ warn(
181
+ `${unusedCount} exported symbol${unusedCount > 1 ? 's have' : ' has'} zero cross-file consumers. Run "codegraph exports <file> --unused" to inspect.`,
182
+ );
182
183
  }
184
+ } catch {
185
+ /* exported column may not exist on older DBs */
186
+ }
187
+ }
188
+
189
+ export async function finalize(ctx: PipelineContext): Promise<void> {
190
+ const { allSymbols, rootDir, isFullBuild, hasEmbeddings, opts } = ctx;
191
+
192
+ const t0 = performance.now();
193
+
194
+ releaseWasmTrees(allSymbols);
195
+
196
+ // Capture a single wall-clock timestamp for the current build — used for
197
+ // both the stale-embeddings comparison and the persisted built_at metadata.
198
+ const buildNow = new Date();
199
+
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;
203
+ info(`Graph built: ${nodeCount} nodes, ${actualEdgeCount} edges`);
204
+ info(`Stored in ${ctx.dbPath}`);
205
+
206
+ detectIncrementalDrift(ctx, nodeCount, actualEdgeCount);
207
+ persistBuildMetadata(ctx, nodeCount, actualEdgeCount, buildNow);
208
+
209
+ // Skip expensive advisory queries for incremental builds — these are
210
+ // informational warnings that don't affect correctness and cost ~40-60ms.
211
+ if (!isFullBuild) {
212
+ debug(
213
+ 'Finalize: skipping advisory queries (orphaned/stale embeddings, unused exports) for incremental build',
214
+ );
215
+ } else {
216
+ runAdvisoryChecks(ctx.db, hasEmbeddings, buildNow);
183
217
  }
184
218
 
185
219
  // Intentionally measured before closeDb / writeJournalHeader / auto-registration:
@@ -192,7 +226,7 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
192
226
  // For small incremental builds, defer the expensive WAL checkpoint to the
193
227
  // next event loop tick. Skip for temp directories (tests) — they rmSync
194
228
  // immediately after build.
195
- const pair = { db, nativeDb: ctx.nativeDb };
229
+ const pair = { db: ctx.db, nativeDb: ctx.nativeDb };
196
230
  const isTempDir = path.resolve(rootDir).startsWith(path.resolve(tmpdir()));
197
231
  if (!isFullBuild && allSymbols.size <= 5 && !isTempDir) {
198
232
  closeDbPairDeferred(pair);
@@ -39,6 +39,12 @@ interface PrecomputedFileData {
39
39
  // ── Native fast-path ─────────────────────────────────────────────────
40
40
 
41
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
+
42
48
  // Use NativeDatabase persistent connection (Phase 6.15+).
43
49
  // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
44
50
  if (!ctx.nativeDb?.bulkInsertNodes) return false;
@@ -52,14 +58,14 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
52
58
  name: string;
53
59
  kind: string;
54
60
  line: number;
55
- endLine?: number | null;
56
- visibility?: string | null;
61
+ endLine?: number;
62
+ visibility?: string;
57
63
  children: Array<{
58
64
  name: string;
59
65
  kind: string;
60
66
  line: number;
61
- endLine?: number | null;
62
- visibility?: string | null;
67
+ endLine?: number;
68
+ visibility?: string;
63
69
  }>;
64
70
  }>;
65
71
  exports: Array<{ name: string; kind: string; line: number }>;
@@ -72,14 +78,14 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
72
78
  name: def.name,
73
79
  kind: def.kind,
74
80
  line: def.line,
75
- endLine: def.endLine ?? null,
76
- visibility: def.visibility ?? null,
81
+ endLine: def.endLine ?? undefined,
82
+ visibility: def.visibility ?? undefined,
77
83
  children: (def.children ?? []).map((c) => ({
78
84
  name: c.name,
79
85
  kind: c.kind,
80
86
  line: c.line,
81
- endLine: c.endLine ?? null,
82
- visibility: c.visibility ?? null,
87
+ endLine: c.endLine ?? undefined,
88
+ visibility: c.visibility ?? undefined,
83
89
  })),
84
90
  })),
85
91
  exports: symbols.exports.map((exp) => ({
@@ -138,7 +144,7 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
138
144
  fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
139
145
  }
140
146
 
141
- return ctx.nativeDb.bulkInsertNodes(batches, fileHashes, removed);
147
+ return ctx.nativeDb!.bulkInsertNodes(batches, fileHashes, removed);
142
148
  }
143
149
 
144
150
  // ── JS fallback: Phase 1 ────────────────────────────────────────────
@@ -330,7 +336,7 @@ function updateFileHashes(
330
336
  // ── Main entry point ────────────────────────────────────────────────
331
337
 
332
338
  export async function insertNodes(ctx: PipelineContext): Promise<void> {
333
- const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
339
+ const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
334
340
 
335
341
  // Populate fileSymbols before any DB writes (used by later stages)
336
342
  for (const [relPath, symbols] of allSymbols) {
@@ -340,11 +346,16 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
340
346
  const t0 = performance.now();
341
347
 
342
348
  // Try native Rust path first — single transaction, no JS↔C overhead
343
- if (ctx.engineName === 'native' && tryNativeInsert(ctx)) {
344
- ctx.timing.insertMs = performance.now() - t0;
345
-
346
- // Removed-file hash cleanup is handled inside the native call
347
- return;
349
+ if (ctx.engineName === 'native') {
350
+ try {
351
+ if (tryNativeInsert(ctx)) {
352
+ ctx.timing.insertMs = performance.now() - t0;
353
+ // Removed-file hash cleanup is handled inside the native call
354
+ return;
355
+ }
356
+ } catch {
357
+ // Native insert failed — fall through to JS implementation
358
+ }
348
359
  }
349
360
 
350
361
  // JS fallback
@@ -355,17 +366,17 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
355
366
 
356
367
  let upsertHash: SqliteStatement | null;
357
368
  try {
358
- upsertHash = db.prepare(
369
+ upsertHash = ctx.db.prepare(
359
370
  'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
360
371
  );
361
372
  } catch {
362
373
  upsertHash = null;
363
374
  }
364
375
 
365
- const insertAll = db.transaction(() => {
366
- insertDefinitionsAndExports(db, allSymbols);
367
- insertChildrenAndEdges(db, allSymbols);
368
- updateFileHashes(db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash);
376
+ const insertAll = ctx.db.transaction(() => {
377
+ insertDefinitionsAndExports(ctx.db, allSymbols);
378
+ insertChildrenAndEdges(ctx.db, allSymbols);
379
+ updateFileHashes(ctx.db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash);
369
380
  });
370
381
 
371
382
  insertAll();
@@ -373,7 +384,7 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
373
384
 
374
385
  // Clean up removed file hashes
375
386
  if (upsertHash && removed.length > 0) {
376
- const deleteHash = db.prepare('DELETE FROM file_hashes WHERE file = ?');
387
+ const deleteHash = ctx.db.prepare('DELETE FROM file_hashes WHERE file = ?');
377
388
  for (const relPath of removed) {
378
389
  deleteHash.run(relPath);
379
390
  }
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { performance } from 'node:perf_hooks';
3
+ import { debug } from '../../../../infrastructure/logger.js';
3
4
  import type { Import } from '../../../../types.js';
4
5
  import { parseFilesAuto } from '../../../parser.js';
5
6
  import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
@@ -132,8 +133,8 @@ export async function resolveImports(ctx: PipelineContext): Promise<void> {
132
133
  );
133
134
  }
134
135
  }
135
- } catch {
136
- /* skip if unreadable */
136
+ } catch (e: unknown) {
137
+ debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
137
138
  }
138
139
  }
139
140
  }
@@ -117,6 +117,35 @@ function matchSubpathPattern(pattern: string, subpath: string): string | null {
117
117
  * Resolve a bare specifier through the package.json exports field.
118
118
  * Returns an absolute path or null.
119
119
  */
120
+ /** Try to resolve a condition target to a file path in packageDir. */
121
+ function tryResolveTarget(target: string | null, packageDir: string): string | null {
122
+ if (!target) return null;
123
+ const resolved = path.resolve(packageDir, target);
124
+ return fs.existsSync(resolved) ? resolved : null;
125
+ }
126
+
127
+ /** Resolve subpath against a subpath map (object with "." keys). */
128
+ function resolveSubpathMap(
129
+ exports: Record<string, unknown>,
130
+ subpath: string,
131
+ packageDir: string,
132
+ ): string | null {
133
+ // Exact match first
134
+ if (subpath in exports) {
135
+ return tryResolveTarget(resolveCondition(exports[subpath]), packageDir);
136
+ }
137
+ // Pattern matching (keys with *)
138
+ for (const [pattern, value] of Object.entries(exports)) {
139
+ if (!pattern.includes('*')) continue;
140
+ const matched = matchSubpathPattern(pattern, subpath);
141
+ if (matched == null) continue;
142
+ const rawTarget = resolveCondition(value);
143
+ if (!rawTarget) continue;
144
+ return tryResolveTarget(rawTarget.replace(/\*/g, matched), packageDir);
145
+ }
146
+ return null;
147
+ }
148
+
120
149
  export function resolveViaExports(specifier: string, rootDir: string): string | null {
121
150
  const parsed = parseBareSpecifier(specifier);
122
151
  if (!parsed) return null;
@@ -131,66 +160,25 @@ export function resolveViaExports(specifier: string, rootDir: string): string |
131
160
 
132
161
  // Simple string exports: "exports": "./index.js"
133
162
  if (typeof exports === 'string') {
134
- if (subpath === '.') {
135
- const resolved = path.resolve(packageDir, exports);
136
- return fs.existsSync(resolved) ? resolved : null;
137
- }
138
- return null;
163
+ return subpath === '.' ? tryResolveTarget(exports, packageDir) : null;
139
164
  }
140
165
 
141
166
  // Array form at top level
142
167
  if (Array.isArray(exports)) {
143
- if (subpath === '.') {
144
- const target = resolveCondition(exports);
145
- if (target) {
146
- const resolved = path.resolve(packageDir, target);
147
- return fs.existsSync(resolved) ? resolved : null;
148
- }
149
- }
150
- return null;
168
+ return subpath === '.' ? tryResolveTarget(resolveCondition(exports), packageDir) : null;
151
169
  }
152
170
 
153
171
  if (typeof exports !== 'object') return null;
154
172
 
155
- // Determine if exports is a conditions object (no keys start with ".")
156
- // or a subpath map (keys start with ".")
173
+ // Determine if exports is a conditions object or a subpath map
157
174
  const keys = Object.keys(exports);
158
175
  const isSubpathMap = keys.length > 0 && keys.some((k) => k.startsWith('.'));
159
176
 
160
177
  if (!isSubpathMap) {
161
- // Conditions object at top level applies to "." subpath only
162
- if (subpath === '.') {
163
- const target = resolveCondition(exports);
164
- if (target) {
165
- const resolved = path.resolve(packageDir, target);
166
- return fs.existsSync(resolved) ? resolved : null;
167
- }
168
- }
169
- return null;
178
+ return subpath === '.' ? tryResolveTarget(resolveCondition(exports), packageDir) : null;
170
179
  }
171
180
 
172
- // Subpath map: try exact match first, then pattern match
173
- if (subpath in exports) {
174
- const target = resolveCondition(exports[subpath]);
175
- if (target) {
176
- const resolved = path.resolve(packageDir, target);
177
- return fs.existsSync(resolved) ? resolved : null;
178
- }
179
- }
180
-
181
- // Pattern matching (keys with *)
182
- for (const [pattern, value] of Object.entries(exports)) {
183
- if (!pattern.includes('*')) continue;
184
- const matched = matchSubpathPattern(pattern, subpath);
185
- if (matched == null) continue;
186
- const rawTarget = resolveCondition(value);
187
- if (!rawTarget) continue;
188
- const target = rawTarget.replace(/\*/g, matched);
189
- const resolved = path.resolve(packageDir, target);
190
- if (fs.existsSync(resolved)) return resolved;
191
- }
192
-
193
- return null;
181
+ return resolveSubpathMap(exports as Record<string, unknown>, subpath, packageDir);
194
182
  }
195
183
 
196
184
  /** Clear the exports cache (for testing). */
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { closeDb, getNodeId as getNodeIdQuery, initSchema, openDb } from '../../db/index.js';
4
- import { info } from '../../infrastructure/logger.js';
4
+ import { debug, info } from '../../infrastructure/logger.js';
5
5
  import { EXTENSIONS, IGNORE_DIRS, normalizePath } from '../../shared/constants.js';
6
6
  import { DbError } from '../../shared/errors.js';
7
7
  import { createParseTreeCache, getActiveEngine } from '../parser.js';
@@ -32,12 +32,10 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
32
32
  ast: false,
33
33
  };
34
34
  const { name: engineName, version: engineVersion } = getActiveEngine(engineOpts);
35
- console.log(
36
- `Watch mode using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`,
37
- );
35
+ info(`Watch mode using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
38
36
 
39
37
  const cache = createParseTreeCache();
40
- console.log(
38
+ info(
41
39
  cache
42
40
  ? 'Incremental parsing enabled (native tree cache)'
43
41
  : 'Incremental parsing unavailable (full re-parse)',
@@ -124,8 +122,8 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
124
122
  }));
125
123
  try {
126
124
  appendJournalEntries(rootDir, entries);
127
- } catch {
128
- /* journal write failure is non-fatal */
125
+ } catch (e: unknown) {
126
+ debug(`Journal write failed (non-fatal): ${(e as Error).message}`);
129
127
  }
130
128
 
131
129
  const changeEvents = updates.map((r) =>
@@ -137,8 +135,8 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
137
135
  );
138
136
  try {
139
137
  appendChangeEvents(rootDir, changeEvents);
140
- } catch {
141
- /* change event write failure is non-fatal */
138
+ } catch (e: unknown) {
139
+ debug(`Change event write failed (non-fatal): ${(e as Error).message}`);
142
140
  }
143
141
  }
144
142
 
@@ -153,8 +151,8 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
153
151
  }
154
152
  }
155
153
 
156
- console.log(`Watching ${rootDir} for changes...`);
157
- console.log('Press Ctrl+C to stop.\n');
154
+ info(`Watching ${rootDir} for changes...`);
155
+ info('Press Ctrl+C to stop.');
158
156
 
159
157
  const watcher = fs.watch(rootDir, { recursive: true }, (_eventType, filename) => {
160
158
  if (!filename) return;
@@ -169,7 +167,7 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
169
167
  });
170
168
 
171
169
  process.on('SIGINT', () => {
172
- console.log('\nStopping watcher...');
170
+ info('Stopping watcher...');
173
171
  watcher.close();
174
172
  // Flush any pending file paths to journal before exit
175
173
  if (pending.size > 0) {
@@ -178,8 +176,8 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
178
176
  }));
179
177
  try {
180
178
  appendJournalEntries(rootDir, entries);
181
- } catch {
182
- /* best-effort */
179
+ } catch (e: unknown) {
180
+ debug(`Journal flush on exit failed (non-fatal): ${(e as Error).message}`);
183
181
  }
184
182
  }
185
183
  if (cache) cache.clear();