@optave/codegraph 3.8.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 (296) hide show
  1. package/README.md +9 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +95 -87
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/watch.d.ts.map +1 -1
  28. package/dist/cli/commands/watch.js +16 -2
  29. package/dist/cli/commands/watch.js.map +1 -1
  30. package/dist/db/connection.d.ts.map +1 -1
  31. package/dist/db/connection.js +29 -26
  32. package/dist/db/connection.js.map +1 -1
  33. package/dist/db/query-builder.d.ts.map +1 -1
  34. package/dist/db/query-builder.js +16 -5
  35. package/dist/db/query-builder.js.map +1 -1
  36. package/dist/db/repository/base.d.ts +10 -0
  37. package/dist/db/repository/base.d.ts.map +1 -1
  38. package/dist/db/repository/base.js +17 -0
  39. package/dist/db/repository/base.js.map +1 -1
  40. package/dist/db/repository/native-repository.d.ts +6 -1
  41. package/dist/db/repository/native-repository.d.ts.map +1 -1
  42. package/dist/db/repository/native-repository.js +77 -1
  43. package/dist/db/repository/native-repository.js.map +1 -1
  44. package/dist/db/repository/nodes.d.ts.map +1 -1
  45. package/dist/db/repository/nodes.js +8 -4
  46. package/dist/db/repository/nodes.js.map +1 -1
  47. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  48. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  49. package/dist/db/repository/sqlite-repository.js +26 -0
  50. package/dist/db/repository/sqlite-repository.js.map +1 -1
  51. package/dist/domain/analysis/brief.d.ts.map +1 -1
  52. package/dist/domain/analysis/brief.js +13 -17
  53. package/dist/domain/analysis/brief.js.map +1 -1
  54. package/dist/domain/analysis/context.d.ts.map +1 -1
  55. package/dist/domain/analysis/context.js +14 -11
  56. package/dist/domain/analysis/context.js.map +1 -1
  57. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  58. package/dist/domain/analysis/dependencies.js +53 -52
  59. package/dist/domain/analysis/dependencies.js.map +1 -1
  60. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  61. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  62. package/dist/domain/analysis/fn-impact.js +33 -31
  63. package/dist/domain/analysis/fn-impact.js.map +1 -1
  64. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  65. package/dist/domain/analysis/implementations.js +11 -19
  66. package/dist/domain/analysis/implementations.js.map +1 -1
  67. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  68. package/dist/domain/analysis/module-map.js +55 -76
  69. package/dist/domain/analysis/module-map.js.map +1 -1
  70. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  71. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  72. package/dist/domain/analysis/query-helpers.js +15 -1
  73. package/dist/domain/analysis/query-helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/pipeline.js +255 -105
  76. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  77. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/stages/build-edges.js +22 -17
  79. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  81. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  83. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  84. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  85. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  86. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  88. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  89. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  90. package/dist/domain/graph/cycles.d.ts +6 -0
  91. package/dist/domain/graph/cycles.d.ts.map +1 -1
  92. package/dist/domain/graph/cycles.js +114 -22
  93. package/dist/domain/graph/cycles.js.map +1 -1
  94. package/dist/domain/graph/resolve.js +1 -1
  95. package/dist/domain/graph/resolve.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts +2 -0
  97. package/dist/domain/graph/watcher.d.ts.map +1 -1
  98. package/dist/domain/graph/watcher.js +170 -75
  99. package/dist/domain/graph/watcher.js.map +1 -1
  100. package/dist/domain/parser.d.ts +0 -5
  101. package/dist/domain/parser.d.ts.map +1 -1
  102. package/dist/domain/parser.js +13 -28
  103. package/dist/domain/parser.js.map +1 -1
  104. package/dist/domain/search/generator.js +1 -1
  105. package/dist/domain/search/generator.js.map +1 -1
  106. package/dist/domain/search/models.d.ts +4 -3
  107. package/dist/domain/search/models.d.ts.map +1 -1
  108. package/dist/domain/search/models.js +18 -5
  109. package/dist/domain/search/models.js.map +1 -1
  110. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  111. package/dist/domain/search/search/hybrid.js +29 -18
  112. package/dist/domain/search/search/hybrid.js.map +1 -1
  113. package/dist/extractors/go.js +36 -33
  114. package/dist/extractors/go.js.map +1 -1
  115. package/dist/extractors/helpers.d.ts.map +1 -1
  116. package/dist/extractors/helpers.js +40 -29
  117. package/dist/extractors/helpers.js.map +1 -1
  118. package/dist/extractors/java.js +58 -46
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.js +46 -45
  121. package/dist/extractors/javascript.js.map +1 -1
  122. package/dist/extractors/kotlin.js +84 -78
  123. package/dist/extractors/kotlin.js.map +1 -1
  124. package/dist/extractors/python.js +29 -24
  125. package/dist/extractors/python.js.map +1 -1
  126. package/dist/extractors/rust.js +41 -32
  127. package/dist/extractors/rust.js.map +1 -1
  128. package/dist/extractors/solidity.js +58 -67
  129. package/dist/extractors/solidity.js.map +1 -1
  130. package/dist/extractors/swift.js +83 -81
  131. package/dist/extractors/swift.js.map +1 -1
  132. package/dist/extractors/zig.js +58 -60
  133. package/dist/extractors/zig.js.map +1 -1
  134. package/dist/features/ast.d.ts +16 -14
  135. package/dist/features/ast.d.ts.map +1 -1
  136. package/dist/features/ast.js +83 -81
  137. package/dist/features/ast.js.map +1 -1
  138. package/dist/features/audit.d.ts.map +1 -1
  139. package/dist/features/audit.js +8 -6
  140. package/dist/features/audit.js.map +1 -1
  141. package/dist/features/branch-compare.d.ts.map +1 -1
  142. package/dist/features/branch-compare.js +69 -72
  143. package/dist/features/branch-compare.js.map +1 -1
  144. package/dist/features/communities.d.ts.map +1 -1
  145. package/dist/features/communities.js +19 -7
  146. package/dist/features/communities.js.map +1 -1
  147. package/dist/features/complexity.d.ts.map +1 -1
  148. package/dist/features/complexity.js +120 -125
  149. package/dist/features/complexity.js.map +1 -1
  150. package/dist/features/dataflow.d.ts.map +1 -1
  151. package/dist/features/dataflow.js +136 -137
  152. package/dist/features/dataflow.js.map +1 -1
  153. package/dist/features/flow.d.ts.map +1 -1
  154. package/dist/features/flow.js +84 -79
  155. package/dist/features/flow.js.map +1 -1
  156. package/dist/features/structure-query.d.ts.map +1 -1
  157. package/dist/features/structure-query.js +69 -65
  158. package/dist/features/structure-query.js.map +1 -1
  159. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  160. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  161. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  162. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  163. package/dist/graph/algorithms/leiden/partition.js +288 -266
  164. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  165. package/dist/graph/model.d.ts.map +1 -1
  166. package/dist/graph/model.js +5 -1
  167. package/dist/graph/model.js.map +1 -1
  168. package/dist/infrastructure/config.d.ts.map +1 -1
  169. package/dist/infrastructure/config.js +6 -4
  170. package/dist/infrastructure/config.js.map +1 -1
  171. package/dist/infrastructure/suppress.d.ts +25 -0
  172. package/dist/infrastructure/suppress.d.ts.map +1 -0
  173. package/dist/infrastructure/suppress.js +43 -0
  174. package/dist/infrastructure/suppress.js.map +1 -0
  175. package/dist/mcp/server.d.ts.map +1 -1
  176. package/dist/mcp/server.js +29 -24
  177. package/dist/mcp/server.js.map +1 -1
  178. package/dist/presentation/dataflow.d.ts.map +1 -1
  179. package/dist/presentation/dataflow.js +47 -38
  180. package/dist/presentation/dataflow.js.map +1 -1
  181. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  182. package/dist/presentation/diff-impact-mermaid.js +60 -51
  183. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  184. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  185. package/dist/presentation/queries-cli/exports.js +20 -14
  186. package/dist/presentation/queries-cli/exports.js.map +1 -1
  187. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  188. package/dist/presentation/queries-cli/impact.js +15 -13
  189. package/dist/presentation/queries-cli/impact.js.map +1 -1
  190. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  191. package/dist/presentation/queries-cli/inspect.js +101 -79
  192. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  193. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/overview.js +25 -16
  195. package/dist/presentation/queries-cli/overview.js.map +1 -1
  196. package/dist/presentation/queries-cli/path.js +26 -20
  197. package/dist/presentation/queries-cli/path.js.map +1 -1
  198. package/dist/presentation/result-formatter.d.ts +10 -0
  199. package/dist/presentation/result-formatter.d.ts.map +1 -1
  200. package/dist/presentation/result-formatter.js +16 -1
  201. package/dist/presentation/result-formatter.js.map +1 -1
  202. package/dist/presentation/viewer.d.ts.map +1 -1
  203. package/dist/presentation/viewer.js +18 -12
  204. package/dist/presentation/viewer.js.map +1 -1
  205. package/dist/shared/errors.d.ts +5 -0
  206. package/dist/shared/errors.d.ts.map +1 -1
  207. package/dist/shared/errors.js +5 -0
  208. package/dist/shared/errors.js.map +1 -1
  209. package/dist/shared/hierarchy.d.ts +8 -2
  210. package/dist/shared/hierarchy.d.ts.map +1 -1
  211. package/dist/shared/hierarchy.js +42 -1
  212. package/dist/shared/hierarchy.js.map +1 -1
  213. package/dist/shared/normalize.d.ts +6 -1
  214. package/dist/shared/normalize.d.ts.map +1 -1
  215. package/dist/shared/normalize.js +20 -12
  216. package/dist/shared/normalize.js.map +1 -1
  217. package/dist/shared/paginate.d.ts +0 -9
  218. package/dist/shared/paginate.d.ts.map +1 -1
  219. package/dist/shared/paginate.js +0 -15
  220. package/dist/shared/paginate.js.map +1 -1
  221. package/dist/types.d.ts +10 -4
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +7 -7
  224. package/src/ast-analysis/engine.ts +126 -105
  225. package/src/ast-analysis/metrics.ts +33 -11
  226. package/src/ast-analysis/shared.ts +33 -24
  227. package/src/ast-analysis/visitor-utils.ts +52 -32
  228. package/src/ast-analysis/visitor.ts +132 -71
  229. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  230. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  231. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  232. package/src/cli/commands/watch.ts +16 -2
  233. package/src/db/connection.ts +29 -28
  234. package/src/db/query-builder.ts +15 -3
  235. package/src/db/repository/base.ts +20 -0
  236. package/src/db/repository/native-repository.ts +79 -1
  237. package/src/db/repository/nodes.ts +13 -8
  238. package/src/db/repository/sqlite-repository.ts +29 -0
  239. package/src/domain/analysis/brief.ts +15 -25
  240. package/src/domain/analysis/context.ts +17 -10
  241. package/src/domain/analysis/dependencies.ts +67 -76
  242. package/src/domain/analysis/fn-impact.ts +36 -43
  243. package/src/domain/analysis/implementations.ts +11 -17
  244. package/src/domain/analysis/module-map.ts +58 -92
  245. package/src/domain/analysis/query-helpers.ts +18 -1
  246. package/src/domain/graph/builder/pipeline.ts +286 -97
  247. package/src/domain/graph/builder/stages/build-edges.ts +22 -18
  248. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  249. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  250. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  251. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  252. package/src/domain/graph/cycles.ts +110 -23
  253. package/src/domain/graph/resolve.ts +1 -1
  254. package/src/domain/graph/watcher.ts +202 -96
  255. package/src/domain/parser.ts +14 -26
  256. package/src/domain/search/generator.ts +1 -1
  257. package/src/domain/search/models.ts +17 -4
  258. package/src/domain/search/search/hybrid.ts +69 -51
  259. package/src/extractors/go.ts +43 -33
  260. package/src/extractors/helpers.ts +37 -23
  261. package/src/extractors/java.ts +66 -47
  262. package/src/extractors/javascript.ts +45 -46
  263. package/src/extractors/kotlin.ts +84 -77
  264. package/src/extractors/python.ts +31 -25
  265. package/src/extractors/rust.ts +37 -29
  266. package/src/extractors/solidity.ts +57 -61
  267. package/src/extractors/swift.ts +81 -80
  268. package/src/extractors/zig.ts +58 -61
  269. package/src/features/ast.ts +130 -110
  270. package/src/features/audit.ts +8 -6
  271. package/src/features/branch-compare.ts +105 -79
  272. package/src/features/communities.ts +25 -10
  273. package/src/features/complexity.ts +171 -134
  274. package/src/features/dataflow.ts +165 -175
  275. package/src/features/flow.ts +129 -92
  276. package/src/features/structure-query.ts +79 -64
  277. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  278. package/src/graph/algorithms/leiden/partition.ts +359 -294
  279. package/src/graph/model.ts +6 -1
  280. package/src/infrastructure/config.ts +6 -4
  281. package/src/infrastructure/suppress.ts +47 -0
  282. package/src/mcp/server.ts +53 -37
  283. package/src/presentation/dataflow.ts +50 -44
  284. package/src/presentation/diff-impact-mermaid.ts +104 -62
  285. package/src/presentation/queries-cli/exports.ts +21 -13
  286. package/src/presentation/queries-cli/impact.ts +15 -13
  287. package/src/presentation/queries-cli/inspect.ts +100 -81
  288. package/src/presentation/queries-cli/overview.ts +26 -16
  289. package/src/presentation/queries-cli/path.ts +33 -25
  290. package/src/presentation/result-formatter.ts +19 -1
  291. package/src/presentation/viewer.ts +42 -14
  292. package/src/shared/errors.ts +6 -0
  293. package/src/shared/hierarchy.ts +50 -2
  294. package/src/shared/normalize.ts +31 -12
  295. package/src/shared/paginate.ts +0 -17
  296. package/src/types.ts +24 -4
@@ -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';
@@ -50,8 +59,10 @@ function initializeEngine(ctx: PipelineContext): void {
50
59
  ? () => {
51
60
  try {
52
61
  ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
53
- } catch {
54
- /* ignore — nativeDb may already be closed */
62
+ } catch (e) {
63
+ debug(
64
+ `resumeJsDb: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
65
+ );
55
66
  }
56
67
  }
57
68
  : undefined,
@@ -132,11 +143,11 @@ function setupPipeline(ctx: PipelineContext): void {
132
143
  // with no cross-library WAL frames (#715, #717).
133
144
  ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
134
145
  } catch (err) {
135
- warn(`NativeDatabase setup failed, falling back to JS: ${(err as Error).message}`);
146
+ warn(`NativeDatabase setup failed, falling back to JS: ${toErrorMessage(err)}`);
136
147
  try {
137
148
  ctx.nativeDb?.close();
138
- } catch {
139
- /* ignore close errors */
149
+ } catch (e) {
150
+ debug(`setupNativeDb: close failed during fallback: ${toErrorMessage(e)}`);
140
151
  }
141
152
  ctx.nativeDb = undefined;
142
153
  }
@@ -185,6 +196,58 @@ function formatTimingResult(ctx: PipelineContext): BuildResult {
185
196
  };
186
197
  }
187
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
+
188
251
  // ── Pipeline stages execution ───────────────────────────────────────────
189
252
 
190
253
  async function runPipelineStages(ctx: PipelineContext): Promise<void> {
@@ -195,26 +258,7 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
195
258
  // that use suspendJsDb/resumeJsDb WAL checkpoint pattern (#696).
196
259
  const hadNativeDb = !!ctx.nativeDb;
197
260
  if (ctx.db && ctx.nativeDb) {
198
- // Checkpoint WAL through rusqlite before closing so better-sqlite3 never
199
- // needs to apply WAL frames written by a different SQLite library (#715, #717).
200
- // Separate try/catch blocks ensure close() always runs even if checkpoint throws,
201
- // preventing a live rusqlite connection from lingering until GC.
202
- try {
203
- ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
204
- } catch {
205
- /* ignore checkpoint errors */
206
- }
207
- try {
208
- ctx.nativeDb.close();
209
- } catch {
210
- /* ignore close errors */
211
- }
212
- ctx.nativeDb = undefined;
213
- // Also clear stale reference in engineOpts to prevent stages from
214
- // calling methods on the closed NativeDatabase.
215
- if (ctx.engineOpts?.nativeDb) {
216
- ctx.engineOpts.nativeDb = undefined;
217
- }
261
+ suspendNativeDb(ctx, 'pre-collect');
218
262
  }
219
263
 
220
264
  await collectFiles(ctx);
@@ -228,44 +272,15 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
228
272
  // guard internally (same pattern as feature modules). Closed again before
229
273
  // resolveImports/buildEdges which don't yet have the guard (#709).
230
274
  if (hadNativeDb && ctx.engineName === 'native') {
231
- const native = loadNative();
232
- if (native?.NativeDatabase) {
233
- try {
234
- ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
235
- } catch {
236
- ctx.nativeDb = undefined;
237
- }
238
- }
275
+ reopenNativeDb(ctx, 'insertNodes');
239
276
  }
240
277
 
241
278
  await insertNodes(ctx);
242
279
 
243
280
  // Close nativeDb after insertNodes — remaining pipeline stages use JS paths.
244
281
  if (ctx.nativeDb && ctx.db) {
245
- // Checkpoint WAL through rusqlite before closing so better-sqlite3 never
246
- // needs to apply WAL frames written by a different SQLite library (#715, #717).
247
- try {
248
- ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
249
- } catch {
250
- /* ignore checkpoint errors */
251
- }
252
- try {
253
- ctx.nativeDb.close();
254
- } catch {
255
- /* ignore close errors */
256
- }
257
- ctx.nativeDb = undefined;
258
- // Reopen better-sqlite3 connection to get a fresh page cache.
259
- // After rusqlite truncates the WAL, better-sqlite3's internal WAL index
260
- // (shared-memory mapping) may reference frames that no longer exist,
261
- // causing SQLITE_CORRUPT on the next read. Closing and reopening
262
- // forces a clean slate — the only reliable cross-library handoff (#715, #736).
263
- try {
264
- ctx.db.close();
265
- } catch {
266
- /* ignore close errors */
267
- }
268
- ctx.db = openDb(ctx.dbPath);
282
+ closeNativeDb(ctx, 'post-insertNodes');
283
+ refreshJsDb(ctx);
269
284
  }
270
285
 
271
286
  await resolveImports(ctx);
@@ -275,41 +290,23 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
275
290
  // Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow)
276
291
  // which use suspendJsDb/resumeJsDb WAL checkpoint before native writes.
277
292
  if (hadNativeDb) {
278
- const native = loadNative();
279
- if (native?.NativeDatabase) {
280
- try {
281
- ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
282
- if (ctx.engineOpts) {
283
- ctx.engineOpts.nativeDb = ctx.nativeDb;
284
- }
285
- } catch {
286
- ctx.nativeDb = undefined;
287
- if (ctx.engineOpts) {
288
- ctx.engineOpts.nativeDb = undefined;
289
- }
290
- }
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;
291
299
  }
292
300
  }
293
301
 
294
302
  await runAnalyses(ctx);
295
303
 
296
- // Close nativeDb after analyses finalize uses JS paths for setBuildMeta
297
- // 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).
298
308
  if (ctx.nativeDb) {
299
- // Checkpoint WAL through rusqlite before closing so better-sqlite3 never
300
- // needs to apply WAL frames written by a different SQLite library (#715, #717).
301
- // Separate try/catch blocks ensure close() always runs even if checkpoint throws.
302
- try {
303
- ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
304
- } catch {
305
- /* ignore checkpoint errors */
306
- }
307
- try {
308
- ctx.nativeDb.close();
309
- } catch {
310
- /* ignore close errors */
311
- }
312
- ctx.nativeDb = undefined;
309
+ refreshJsDb(ctx);
313
310
  }
314
311
 
315
312
  await finalize(ctx);
@@ -338,7 +335,27 @@ export async function buildGraph(
338
335
  // When available, run the entire build pipeline in Rust with zero
339
336
  // napi crossings (eliminates WAL dual-connection dance). Falls back
340
337
  // to the JS pipeline on failure or when native is unavailable.
341
- const forceJs = process.env.CODEGRAPH_FORCE_JS_PIPELINE === '1';
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
+ }
342
359
  if (!forceJs && ctx.nativeDb?.buildGraph) {
343
360
  try {
344
361
  const resultJson = ctx.nativeDb.buildGraph(
@@ -353,21 +370,193 @@ export async function buildGraph(
353
370
  nodeCount?: number;
354
371
  edgeCount?: number;
355
372
  fileCount?: number;
373
+ changedFiles?: string[];
374
+ changedCount?: number;
375
+ removedCount?: number;
376
+ isFullBuild?: boolean;
356
377
  };
357
378
 
358
379
  if (result.earlyExit) {
380
+ info('No changes detected');
359
381
  closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
360
382
  return;
361
383
  }
362
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
+
363
392
  // Map Rust timing fields to the JS BuildResult format.
364
393
  // Rust handles collect+detect+parse+insert+resolve+edges+structure+roles.
365
- // AST/complexity/CFG/dataflow analyses are not yet ported to Rust.
366
394
  const p = result.phases;
367
- closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
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
+
368
412
  info(
369
413
  `Native build orchestrator completed: ${result.nodeCount ?? 0} nodes, ${result.edgeCount ?? 0} edges, ${result.fileCount ?? 0} files`,
370
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 });
371
560
  return {
372
561
  phases: {
373
562
  setupMs: +((p.setupMs ?? 0) + (p.collectMs ?? 0) + (p.detectMs ?? 0)).toFixed(1),
@@ -377,16 +566,16 @@ export async function buildGraph(
377
566
  edgesMs: +(p.edgesMs ?? 0).toFixed(1),
378
567
  structureMs: +(p.structureMs ?? 0).toFixed(1),
379
568
  rolesMs: +(p.rolesMs ?? 0).toFixed(1),
380
- astMs: 0,
381
- complexityMs: 0,
382
- cfgMs: 0,
383
- dataflowMs: 0,
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),
384
573
  finalizeMs: +(p.finalizeMs ?? 0).toFixed(1),
385
574
  },
386
575
  };
387
576
  } catch (err) {
388
577
  warn(
389
- `Native build orchestrator failed, falling back to JS pipeline: ${(err as Error).message}`,
578
+ `Native build orchestrator failed, falling back to JS pipeline: ${toErrorMessage(err)}`,
390
579
  );
391
580
  // Fall through to JS pipeline
392
581
  }
@@ -197,8 +197,13 @@ function buildImportEdgesNative(
197
197
  };
198
198
  const fileNodeRowCache = new Map<string, { id: number }>();
199
199
 
200
- // 2. Pre-resolve all imports and collect supplemental resolved entries
201
- const supplementalResolved: Array<{ key: string; resolvedPath: string }> = [];
200
+ // 2. Pre-resolve all imports and build resolved imports array.
201
+ // Keys use forward-slash-normalized rootDir + "/" + relPath to match the Rust
202
+ // lookup format (format!("{}/{}", root_dir.replace('\\', "/"), file)).
203
+ // On Windows, rootDir has backslashes but Rust normalizes them — the JS side
204
+ // must do the same or every resolve key lookup misses (#750).
205
+ const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
206
+ const fwdRootDir = rootDir.replace(/\\/g, '/');
202
207
 
203
208
  for (const [relPath, symbols] of fileSymbols) {
204
209
  const fileNodeRow = addFileNodeId(relPath);
@@ -218,11 +223,8 @@ function buildImportEdgesNative(
218
223
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
219
224
  addFileNodeId(resolvedPath);
220
225
 
221
- // Check if this resolution is in batchResolved; if not, add supplemental
222
- const resolveKey = `${path.join(rootDir, relPath)}|${imp.source}`;
223
- if (!ctx.batchResolved?.has(resolveKey)) {
224
- supplementalResolved.push({ key: resolveKey, resolvedPath });
225
- }
226
+ // Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
227
+ resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
226
228
 
227
229
  importInfos.push({
228
230
  source: imp.source,
@@ -243,17 +245,6 @@ function buildImportEdgesNative(
243
245
  });
244
246
  }
245
247
 
246
- // 3. Flatten batchResolved + supplemental into resolved imports array
247
- const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
248
- if (ctx.batchResolved) {
249
- for (const [key, resolvedPath] of ctx.batchResolved) {
250
- resolvedImports.push({ key, resolvedPath });
251
- }
252
- }
253
- for (const entry of supplementalResolved) {
254
- resolvedImports.push(entry);
255
- }
256
-
257
248
  // 4. Flatten reexportMap
258
249
  const fileReexports: Array<{
259
250
  file: string;
@@ -748,7 +739,20 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
748
739
  const useNativeImportEdges =
749
740
  native?.buildImportEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
750
741
  if (useNativeImportEdges) {
742
+ const beforeLen = allEdgeRows.length;
751
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
+ }
752
756
  } else {
753
757
  buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
754
758
  }
@@ -349,7 +349,7 @@ function findReverseDependencies(
349
349
  const changedArray = [...changedRelPaths];
350
350
  const nativeResults = nativeDb.findReverseDependencies(changedArray);
351
351
  for (const dep of nativeResults) {
352
- const absPath = path.join(rootDir, dep);
352
+ const absPath = path.isAbsolute(dep) ? dep : path.join(rootDir, dep);
353
353
  if (fs.existsSync(absPath)) {
354
354
  reverseDeps.add(dep);
355
355
  }
@@ -366,7 +366,7 @@ function findReverseDependencies(
366
366
  for (const relPath of changedRelPaths) {
367
367
  for (const row of findReverseDepsStmt.all(relPath) as Array<{ file: string }>) {
368
368
  if (!changedRelPaths.has(row.file) && !reverseDeps.has(row.file)) {
369
- const absPath = path.join(rootDir, row.file);
369
+ const absPath = path.isAbsolute(row.file) ? row.file : path.join(rootDir, row.file);
370
370
  if (fs.existsSync(absPath)) {
371
371
  reverseDeps.add(row.file);
372
372
  }
@@ -86,7 +86,7 @@ function persistBuildMetadata(
86
86
  ctx.nativeDb!.setBuildMeta(
87
87
  Object.entries({
88
88
  engine: ctx.engineName,
89
- engine_version: ctx.engineVersion || '',
89
+ engine_version: CODEGRAPH_VERSION,
90
90
  codegraph_version: CODEGRAPH_VERSION,
91
91
  schema_version: String(ctx.schemaVersion),
92
92
  built_at: buildNow.toISOString(),
@@ -97,7 +97,7 @@ function persistBuildMetadata(
97
97
  } else {
98
98
  setBuildMeta(ctx.db, {
99
99
  engine: ctx.engineName,
100
- engine_version: ctx.engineVersion || '',
100
+ engine_version: CODEGRAPH_VERSION,
101
101
  codegraph_version: CODEGRAPH_VERSION,
102
102
  schema_version: String(ctx.schemaVersion),
103
103
  built_at: buildNow.toISOString(),