@optave/codegraph 3.4.1 → 3.6.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 (349) hide show
  1. package/README.md +50 -28
  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/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +1 -0
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +116 -35
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  12. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  13. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  14. package/dist/db/better-sqlite3.d.ts +3 -0
  15. package/dist/db/better-sqlite3.d.ts.map +1 -0
  16. package/dist/db/better-sqlite3.js +19 -0
  17. package/dist/db/better-sqlite3.js.map +1 -0
  18. package/dist/db/connection.d.ts +25 -4
  19. package/dist/db/connection.d.ts.map +1 -1
  20. package/dist/db/connection.js +125 -23
  21. package/dist/db/connection.js.map +1 -1
  22. package/dist/db/index.d.ts +2 -2
  23. package/dist/db/index.d.ts.map +1 -1
  24. package/dist/db/index.js +1 -1
  25. package/dist/db/index.js.map +1 -1
  26. package/dist/db/migrations.d.ts.map +1 -1
  27. package/dist/db/migrations.js +40 -32
  28. package/dist/db/migrations.js.map +1 -1
  29. package/dist/db/query-builder.d.ts +5 -5
  30. package/dist/db/query-builder.d.ts.map +1 -1
  31. package/dist/db/query-builder.js +20 -4
  32. package/dist/db/query-builder.js.map +1 -1
  33. package/dist/db/repository/index.d.ts +1 -0
  34. package/dist/db/repository/index.d.ts.map +1 -1
  35. package/dist/db/repository/index.js +1 -0
  36. package/dist/db/repository/index.js.map +1 -1
  37. package/dist/db/repository/native-repository.d.ts +58 -0
  38. package/dist/db/repository/native-repository.d.ts.map +1 -0
  39. package/dist/db/repository/native-repository.js +261 -0
  40. package/dist/db/repository/native-repository.js.map +1 -0
  41. package/dist/db/repository/nodes.d.ts +4 -4
  42. package/dist/db/repository/nodes.d.ts.map +1 -1
  43. package/dist/db/repository/nodes.js +6 -6
  44. package/dist/db/repository/nodes.js.map +1 -1
  45. package/dist/domain/analysis/context.d.ts.map +1 -1
  46. package/dist/domain/analysis/context.js +51 -66
  47. package/dist/domain/analysis/context.js.map +1 -1
  48. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  49. package/dist/domain/analysis/dependencies.js +62 -70
  50. package/dist/domain/analysis/dependencies.js.map +1 -1
  51. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  52. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  53. package/dist/domain/analysis/exports.d.ts.map +1 -1
  54. package/dist/domain/analysis/exports.js +29 -33
  55. package/dist/domain/analysis/exports.js.map +1 -1
  56. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  57. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  58. package/dist/domain/analysis/fn-impact.js +35 -65
  59. package/dist/domain/analysis/fn-impact.js.map +1 -1
  60. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  61. package/dist/domain/analysis/module-map.js +91 -6
  62. package/dist/domain/analysis/module-map.js.map +1 -1
  63. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  64. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  65. package/dist/domain/analysis/query-helpers.js +27 -0
  66. package/dist/domain/analysis/query-helpers.js.map +1 -0
  67. package/dist/domain/graph/builder/context.d.ts +2 -1
  68. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  69. package/dist/domain/graph/builder/context.js +1 -0
  70. package/dist/domain/graph/builder/context.js.map +1 -1
  71. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  72. package/dist/domain/graph/builder/helpers.js +15 -9
  73. package/dist/domain/graph/builder/helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/incremental.js +3 -2
  76. package/dist/domain/graph/builder/incremental.js.map +1 -1
  77. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/pipeline.js +95 -7
  79. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  81. package/dist/domain/graph/builder/stages/build-edges.js +101 -57
  82. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  83. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  84. package/dist/domain/graph/builder/stages/build-structure.js +33 -3
  85. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  86. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/collect-files.js +70 -6
  88. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  90. package/dist/domain/graph/builder/stages/detect-changes.js +36 -14
  91. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  92. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  93. package/dist/domain/graph/builder/stages/finalize.js +130 -88
  94. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  95. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  96. package/dist/domain/graph/builder/stages/insert-nodes.js +124 -16
  97. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  98. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  99. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  100. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  101. package/dist/domain/graph/resolve.d.ts +0 -4
  102. package/dist/domain/graph/resolve.d.ts.map +1 -1
  103. package/dist/domain/graph/resolve.js +32 -48
  104. package/dist/domain/graph/resolve.js.map +1 -1
  105. package/dist/domain/graph/watcher.d.ts.map +1 -1
  106. package/dist/domain/graph/watcher.js +12 -12
  107. package/dist/domain/graph/watcher.js.map +1 -1
  108. package/dist/domain/parser.d.ts +1 -1
  109. package/dist/domain/parser.d.ts.map +1 -1
  110. package/dist/domain/parser.js +165 -101
  111. package/dist/domain/parser.js.map +1 -1
  112. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  113. package/dist/domain/search/search/cli-formatter.js +88 -83
  114. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  115. package/dist/extractors/bash.d.ts +6 -0
  116. package/dist/extractors/bash.d.ts.map +1 -0
  117. package/dist/extractors/bash.js +91 -0
  118. package/dist/extractors/bash.js.map +1 -0
  119. package/dist/extractors/c.d.ts +6 -0
  120. package/dist/extractors/c.d.ts.map +1 -0
  121. package/dist/extractors/c.js +204 -0
  122. package/dist/extractors/c.js.map +1 -0
  123. package/dist/extractors/cpp.d.ts +6 -0
  124. package/dist/extractors/cpp.d.ts.map +1 -0
  125. package/dist/extractors/cpp.js +283 -0
  126. package/dist/extractors/cpp.js.map +1 -0
  127. package/dist/extractors/csharp.d.ts.map +1 -1
  128. package/dist/extractors/csharp.js +42 -54
  129. package/dist/extractors/csharp.js.map +1 -1
  130. package/dist/extractors/go.d.ts.map +1 -1
  131. package/dist/extractors/go.js +126 -130
  132. package/dist/extractors/go.js.map +1 -1
  133. package/dist/extractors/hcl.js +6 -6
  134. package/dist/extractors/hcl.js.map +1 -1
  135. package/dist/extractors/helpers.d.ts +32 -1
  136. package/dist/extractors/helpers.d.ts.map +1 -1
  137. package/dist/extractors/helpers.js +74 -0
  138. package/dist/extractors/helpers.js.map +1 -1
  139. package/dist/extractors/index.d.ts +6 -0
  140. package/dist/extractors/index.d.ts.map +1 -1
  141. package/dist/extractors/index.js +6 -0
  142. package/dist/extractors/index.js.map +1 -1
  143. package/dist/extractors/java.d.ts.map +1 -1
  144. package/dist/extractors/java.js +32 -47
  145. package/dist/extractors/java.js.map +1 -1
  146. package/dist/extractors/javascript.d.ts.map +1 -1
  147. package/dist/extractors/javascript.js +359 -330
  148. package/dist/extractors/javascript.js.map +1 -1
  149. package/dist/extractors/kotlin.d.ts +6 -0
  150. package/dist/extractors/kotlin.d.ts.map +1 -0
  151. package/dist/extractors/kotlin.js +275 -0
  152. package/dist/extractors/kotlin.js.map +1 -0
  153. package/dist/extractors/php.d.ts.map +1 -1
  154. package/dist/extractors/php.js +39 -44
  155. package/dist/extractors/php.js.map +1 -1
  156. package/dist/extractors/python.d.ts.map +1 -1
  157. package/dist/extractors/python.js +75 -93
  158. package/dist/extractors/python.js.map +1 -1
  159. package/dist/extractors/ruby.js +6 -13
  160. package/dist/extractors/ruby.js.map +1 -1
  161. package/dist/extractors/rust.d.ts.map +1 -1
  162. package/dist/extractors/rust.js +58 -82
  163. package/dist/extractors/rust.js.map +1 -1
  164. package/dist/extractors/scala.d.ts +6 -0
  165. package/dist/extractors/scala.d.ts.map +1 -0
  166. package/dist/extractors/scala.js +269 -0
  167. package/dist/extractors/scala.js.map +1 -0
  168. package/dist/extractors/swift.d.ts +6 -0
  169. package/dist/extractors/swift.d.ts.map +1 -0
  170. package/dist/extractors/swift.js +275 -0
  171. package/dist/extractors/swift.js.map +1 -0
  172. package/dist/features/ast.d.ts +16 -1
  173. package/dist/features/ast.d.ts.map +1 -1
  174. package/dist/features/ast.js +45 -23
  175. package/dist/features/ast.js.map +1 -1
  176. package/dist/features/audit.d.ts.map +1 -1
  177. package/dist/features/audit.js +17 -21
  178. package/dist/features/audit.js.map +1 -1
  179. package/dist/features/branch-compare.d.ts.map +1 -1
  180. package/dist/features/branch-compare.js +50 -4
  181. package/dist/features/branch-compare.js.map +1 -1
  182. package/dist/features/cfg.d.ts +7 -1
  183. package/dist/features/cfg.d.ts.map +1 -1
  184. package/dist/features/cfg.js +118 -62
  185. package/dist/features/cfg.js.map +1 -1
  186. package/dist/features/check.d.ts.map +1 -1
  187. package/dist/features/check.js +79 -62
  188. package/dist/features/check.js.map +1 -1
  189. package/dist/features/complexity-query.d.ts.map +1 -1
  190. package/dist/features/complexity-query.js +142 -137
  191. package/dist/features/complexity-query.js.map +1 -1
  192. package/dist/features/complexity.d.ts +7 -1
  193. package/dist/features/complexity.d.ts.map +1 -1
  194. package/dist/features/complexity.js +62 -1
  195. package/dist/features/complexity.js.map +1 -1
  196. package/dist/features/dataflow.d.ts +7 -1
  197. package/dist/features/dataflow.d.ts.map +1 -1
  198. package/dist/features/dataflow.js +356 -188
  199. package/dist/features/dataflow.js.map +1 -1
  200. package/dist/features/graph-enrichment.d.ts.map +1 -1
  201. package/dist/features/graph-enrichment.js +117 -104
  202. package/dist/features/graph-enrichment.js.map +1 -1
  203. package/dist/features/sequence.d.ts.map +1 -1
  204. package/dist/features/sequence.js +25 -4
  205. package/dist/features/sequence.js.map +1 -1
  206. package/dist/features/snapshot.d.ts.map +1 -1
  207. package/dist/features/snapshot.js +2 -1
  208. package/dist/features/snapshot.js.map +1 -1
  209. package/dist/features/structure-query.d.ts.map +1 -1
  210. package/dist/features/structure-query.js +29 -4
  211. package/dist/features/structure-query.js.map +1 -1
  212. package/dist/features/structure.d.ts.map +1 -1
  213. package/dist/features/structure.js +35 -15
  214. package/dist/features/structure.js.map +1 -1
  215. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  216. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  217. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  218. package/dist/graph/algorithms/leiden/index.js +43 -28
  219. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  220. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  221. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  222. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  223. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  224. package/dist/graph/algorithms/leiden/partition.js +89 -106
  225. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  226. package/dist/graph/model.d.ts +2 -0
  227. package/dist/graph/model.d.ts.map +1 -1
  228. package/dist/graph/model.js +20 -8
  229. package/dist/graph/model.js.map +1 -1
  230. package/dist/infrastructure/config.d.ts +0 -8
  231. package/dist/infrastructure/config.d.ts.map +1 -1
  232. package/dist/infrastructure/config.js +73 -62
  233. package/dist/infrastructure/config.js.map +1 -1
  234. package/dist/infrastructure/registry.d.ts +0 -8
  235. package/dist/infrastructure/registry.d.ts.map +1 -1
  236. package/dist/infrastructure/registry.js +12 -14
  237. package/dist/infrastructure/registry.js.map +1 -1
  238. package/dist/mcp/server.d.ts.map +1 -1
  239. package/dist/mcp/server.js +47 -45
  240. package/dist/mcp/server.js.map +1 -1
  241. package/dist/presentation/audit.d.ts.map +1 -1
  242. package/dist/presentation/audit.js +61 -57
  243. package/dist/presentation/audit.js.map +1 -1
  244. package/dist/presentation/branch-compare.d.ts.map +1 -1
  245. package/dist/presentation/branch-compare.js +56 -38
  246. package/dist/presentation/branch-compare.js.map +1 -1
  247. package/dist/presentation/check.d.ts.map +1 -1
  248. package/dist/presentation/check.js +30 -32
  249. package/dist/presentation/check.js.map +1 -1
  250. package/dist/presentation/colors.d.ts.map +1 -1
  251. package/dist/presentation/colors.js +2 -0
  252. package/dist/presentation/colors.js.map +1 -1
  253. package/dist/presentation/complexity.d.ts.map +1 -1
  254. package/dist/presentation/complexity.js +25 -19
  255. package/dist/presentation/complexity.js.map +1 -1
  256. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  257. package/dist/presentation/queries-cli/exports.js +15 -15
  258. package/dist/presentation/queries-cli/exports.js.map +1 -1
  259. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  260. package/dist/presentation/queries-cli/impact.js +29 -19
  261. package/dist/presentation/queries-cli/impact.js.map +1 -1
  262. package/dist/types.d.ts +406 -3
  263. package/dist/types.d.ts.map +1 -1
  264. package/grammars/tree-sitter-bash.wasm +0 -0
  265. package/grammars/tree-sitter-c.wasm +0 -0
  266. package/grammars/tree-sitter-cpp.wasm +0 -0
  267. package/grammars/tree-sitter-kotlin.wasm +0 -0
  268. package/grammars/tree-sitter-scala.wasm +0 -0
  269. package/grammars/tree-sitter-swift.wasm +0 -0
  270. package/package.json +67 -11
  271. package/src/ast-analysis/engine.ts +147 -138
  272. package/src/ast-analysis/rules/javascript.ts +1 -0
  273. package/src/ast-analysis/visitors/ast-store-visitor.ts +116 -34
  274. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  275. package/src/db/better-sqlite3.ts +20 -0
  276. package/src/db/connection.ts +148 -26
  277. package/src/db/index.ts +4 -1
  278. package/src/db/migrations.ts +38 -32
  279. package/src/db/query-builder.ts +30 -5
  280. package/src/db/repository/index.ts +1 -0
  281. package/src/db/repository/native-repository.ts +361 -0
  282. package/src/db/repository/nodes.ts +7 -3
  283. package/src/domain/analysis/context.ts +73 -75
  284. package/src/domain/analysis/dependencies.ts +78 -68
  285. package/src/domain/analysis/exports.ts +45 -34
  286. package/src/domain/analysis/fn-impact.ts +67 -64
  287. package/src/domain/analysis/module-map.ts +103 -8
  288. package/src/domain/analysis/query-helpers.ts +35 -0
  289. package/src/domain/graph/builder/context.ts +2 -0
  290. package/src/domain/graph/builder/helpers.ts +12 -6
  291. package/src/domain/graph/builder/incremental.ts +3 -2
  292. package/src/domain/graph/builder/pipeline.ts +98 -6
  293. package/src/domain/graph/builder/stages/build-edges.ts +116 -83
  294. package/src/domain/graph/builder/stages/build-structure.ts +46 -8
  295. package/src/domain/graph/builder/stages/collect-files.ts +83 -6
  296. package/src/domain/graph/builder/stages/detect-changes.ts +44 -21
  297. package/src/domain/graph/builder/stages/finalize.ts +172 -109
  298. package/src/domain/graph/builder/stages/insert-nodes.ts +147 -17
  299. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  300. package/src/domain/graph/resolve.ts +34 -46
  301. package/src/domain/graph/watcher.ts +12 -14
  302. package/src/domain/parser.ts +169 -97
  303. package/src/domain/search/search/cli-formatter.ts +121 -94
  304. package/src/extractors/bash.ts +97 -0
  305. package/src/extractors/c.ts +212 -0
  306. package/src/extractors/cpp.ts +298 -0
  307. package/src/extractors/csharp.ts +53 -56
  308. package/src/extractors/go.ts +152 -134
  309. package/src/extractors/hcl.ts +6 -6
  310. package/src/extractors/helpers.ts +93 -1
  311. package/src/extractors/index.ts +6 -0
  312. package/src/extractors/java.ts +43 -48
  313. package/src/extractors/javascript.ts +382 -317
  314. package/src/extractors/kotlin.ts +293 -0
  315. package/src/extractors/php.ts +46 -40
  316. package/src/extractors/python.ts +81 -104
  317. package/src/extractors/ruby.ts +6 -13
  318. package/src/extractors/rust.ts +65 -84
  319. package/src/extractors/scala.ts +285 -0
  320. package/src/extractors/swift.ts +293 -0
  321. package/src/features/ast.ts +74 -24
  322. package/src/features/audit.ts +24 -20
  323. package/src/features/branch-compare.ts +54 -5
  324. package/src/features/cfg.ts +158 -65
  325. package/src/features/check.ts +90 -74
  326. package/src/features/complexity-query.ts +181 -163
  327. package/src/features/complexity.ts +64 -1
  328. package/src/features/dataflow.ts +462 -217
  329. package/src/features/graph-enrichment.ts +161 -117
  330. package/src/features/sequence.ts +27 -4
  331. package/src/features/snapshot.ts +2 -1
  332. package/src/features/structure-query.ts +43 -4
  333. package/src/features/structure.ts +50 -22
  334. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  335. package/src/graph/algorithms/leiden/index.ts +67 -28
  336. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  337. package/src/graph/algorithms/leiden/partition.ts +131 -98
  338. package/src/graph/model.ts +19 -7
  339. package/src/infrastructure/config.ts +60 -58
  340. package/src/infrastructure/registry.ts +17 -14
  341. package/src/mcp/server.ts +48 -47
  342. package/src/presentation/audit.ts +72 -67
  343. package/src/presentation/branch-compare.ts +54 -50
  344. package/src/presentation/check.ts +34 -34
  345. package/src/presentation/colors.ts +2 -0
  346. package/src/presentation/complexity.ts +39 -33
  347. package/src/presentation/queries-cli/exports.ts +17 -17
  348. package/src/presentation/queries-cli/impact.ts +30 -22
  349. package/src/types.ts +458 -3
@@ -0,0 +1,35 @@
1
+ import { openReadonlyOrFail } from '../../db/index.js';
2
+ import { loadConfig } from '../../infrastructure/config.js';
3
+ import type { BetterSqlite3Database, CodegraphConfig } from '../../types.js';
4
+
5
+ /**
6
+ * Open a readonly DB connection, run `fn`, and close the DB on completion.
7
+ * Eliminates the duplicated `openReadonlyOrFail` + `try/finally/db.close()` pattern
8
+ * that appears in every analysis query function.
9
+ */
10
+ export function withReadonlyDb<T>(
11
+ customDbPath: string | undefined,
12
+ fn: (db: BetterSqlite3Database) => T,
13
+ ): T {
14
+ const db = openReadonlyOrFail(customDbPath);
15
+ try {
16
+ return fn(db);
17
+ } finally {
18
+ db.close();
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Resolve common analysis options into a normalized form.
24
+ * Shared across fn-impact, context, dependencies, and exports modules.
25
+ */
26
+ export function resolveAnalysisOpts(opts: { noTests?: boolean; config?: CodegraphConfig }): {
27
+ noTests: boolean;
28
+ config: CodegraphConfig;
29
+ displayOpts: Record<string, unknown>;
30
+ } {
31
+ const noTests = opts.noTests || false;
32
+ const config = opts.config || loadConfig();
33
+ const displayOpts = config.display || {};
34
+ return { noTests, config, displayOpts };
35
+ }
@@ -12,6 +12,7 @@ import type {
12
12
  ExtractorOutput,
13
13
  FileToParse,
14
14
  MetadataUpdate,
15
+ NativeDatabase,
15
16
  NodeRow,
16
17
  ParseChange,
17
18
  PathAliases,
@@ -31,6 +32,7 @@ export class PipelineContext {
31
32
  incremental!: boolean;
32
33
  forceFullRebuild: boolean = false;
33
34
  schemaVersion!: number;
35
+ nativeDb?: NativeDatabase;
34
36
 
35
37
  // ── File collection (set by collectFiles stage) ────────────────────
36
38
  allFiles!: string[];
@@ -47,6 +47,17 @@ export const BUILTIN_RECEIVERS: Set<string> = new Set([
47
47
  'require',
48
48
  ]);
49
49
 
50
+ /** Check if a directory entry should be skipped (ignored dirs, dotfiles). */
51
+ function shouldSkipEntry(entry: fs.Dirent, extraIgnore: Set<string> | null): boolean {
52
+ if (entry.name.startsWith('.') && entry.name !== '.') {
53
+ if (IGNORE_DIRS.has(entry.name)) return true;
54
+ if (entry.isDirectory()) return true;
55
+ }
56
+ if (IGNORE_DIRS.has(entry.name)) return true;
57
+ if (extraIgnore?.has(entry.name)) return true;
58
+ return false;
59
+ }
60
+
50
61
  /**
51
62
  * Recursively collect all source files under `dir`.
52
63
  * When `directories` is a Set, also tracks which directories contain files.
@@ -100,12 +111,7 @@ export function collectFiles(
100
111
  }
101
112
 
102
113
  for (const entry of entries) {
103
- if (entry.name.startsWith('.') && entry.name !== '.') {
104
- if (IGNORE_DIRS.has(entry.name)) continue;
105
- if (entry.isDirectory()) continue;
106
- }
107
- if (IGNORE_DIRS.has(entry.name)) continue;
108
- if (extraIgnore?.has(entry.name)) continue;
114
+ if (shouldSkipEntry(entry, extraIgnore)) continue;
109
115
 
110
116
  const full = path.join(dir, entry.name);
111
117
  if (entry.isDirectory()) {
@@ -10,7 +10,7 @@
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
12
12
  import { bulkNodeIdsByFile } from '../../../db/index.js';
13
- import { warn } from '../../../infrastructure/logger.js';
13
+ import { debug, warn } from '../../../infrastructure/logger.js';
14
14
  import { normalizePath } from '../../../shared/constants.js';
15
15
  import type {
16
16
  BetterSqlite3Database,
@@ -154,7 +154,8 @@ async function parseReverseDep(
154
154
  let code: string;
155
155
  try {
156
156
  code = readFileSafe(absPath);
157
- } catch {
157
+ } catch (e: unknown) {
158
+ debug(`parseReverseDep: cannot read ${absPath}: ${e instanceof Error ? e.message : String(e)}`);
158
159
  return null;
159
160
  }
160
161
 
@@ -6,9 +6,10 @@
6
6
  */
7
7
  import path from 'node:path';
8
8
  import { performance } from 'node:perf_hooks';
9
- import { closeDb, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js';
9
+ import { closeDbPair, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js';
10
10
  import { detectWorkspaces, loadConfig } from '../../../infrastructure/config.js';
11
11
  import { info, warn } from '../../../infrastructure/logger.js';
12
+ import { loadNative } from '../../../infrastructure/native.js';
12
13
  import { CODEGRAPH_VERSION } from '../../../shared/version.js';
13
14
  import type { BuildGraphOpts, BuildResult } from '../../../types.js';
14
15
  import { getActiveEngine } from '../../parser.js';
@@ -33,6 +34,19 @@ function initializeEngine(ctx: PipelineContext): void {
33
34
  engine: ctx.opts.engine || 'auto',
34
35
  dataflow: ctx.opts.dataflow !== false,
35
36
  ast: ctx.opts.ast !== false,
37
+ nativeDb: ctx.nativeDb,
38
+ // WAL checkpoint callbacks for dual-connection WAL guard (#696).
39
+ // Feature modules (ast, cfg, complexity, dataflow) receive `db` as a
40
+ // parameter and cannot tolerate close/reopen (stale reference). Instead,
41
+ // checkpoint the WAL so native writes start with a clean slate. Features
42
+ // return early on native success and never read native-written WAL data
43
+ // through the JS connection, so a post-write checkpoint is unnecessary.
44
+ suspendJsDb: ctx.nativeDb
45
+ ? () => {
46
+ ctx.db.pragma('wal_checkpoint(TRUNCATE)');
47
+ }
48
+ : undefined,
49
+ resumeJsDb: ctx.nativeDb ? () => {} : undefined,
36
50
  };
37
51
  const { name: engineName, version: engineVersion } = getActiveEngine(ctx.engineOpts);
38
52
  ctx.engineName = engineName as 'native' | 'wasm';
@@ -46,19 +60,25 @@ function checkEngineSchemaMismatch(ctx: PipelineContext): void {
46
60
  ctx.forceFullRebuild = false;
47
61
  if (!ctx.incremental) return;
48
62
 
49
- const prevEngine = getBuildMeta(ctx.db, 'engine');
63
+ // Route metadata reads through NativeDatabase only when using the native engine,
64
+ // to avoid dual-SQLite WAL conflicts (rusqlite + better-sqlite3 on same file).
65
+ const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
66
+ const meta = (key: string): string | null =>
67
+ useNativeDb ? ctx.nativeDb!.getBuildMeta(key) : getBuildMeta(ctx.db, key);
68
+
69
+ const prevEngine = meta('engine');
50
70
  if (prevEngine && prevEngine !== ctx.engineName) {
51
71
  info(`Engine changed (${prevEngine} → ${ctx.engineName}), promoting to full rebuild.`);
52
72
  ctx.forceFullRebuild = true;
53
73
  }
54
- const prevSchema = getBuildMeta(ctx.db, 'schema_version');
74
+ const prevSchema = meta('schema_version');
55
75
  if (prevSchema && Number(prevSchema) !== ctx.schemaVersion) {
56
76
  info(
57
77
  `Schema version changed (${prevSchema} → ${ctx.schemaVersion}), promoting to full rebuild.`,
58
78
  );
59
79
  ctx.forceFullRebuild = true;
60
80
  }
61
- const prevVersion = getBuildMeta(ctx.db, 'codegraph_version');
81
+ const prevVersion = meta('codegraph_version');
62
82
  if (prevVersion && prevVersion !== CODEGRAPH_VERSION) {
63
83
  info(
64
84
  `Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`,
@@ -91,7 +111,25 @@ function setupPipeline(ctx: PipelineContext): void {
91
111
  ctx.rootDir = path.resolve(ctx.rootDir);
92
112
  ctx.dbPath = path.join(ctx.rootDir, '.codegraph', 'graph.db');
93
113
  ctx.db = openDb(ctx.dbPath);
94
- initSchema(ctx.db);
114
+
115
+ // Use NativeDatabase for schema init when native engine is available (Phase 6.13).
116
+ // better-sqlite3 (ctx.db) is still always opened — needed for queries and stages
117
+ // that haven't been migrated to rusqlite yet.
118
+ const native = loadNative();
119
+ if (native?.NativeDatabase) {
120
+ try {
121
+ ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
122
+ ctx.nativeDb.initSchema();
123
+ } catch (err) {
124
+ warn(`NativeDatabase init failed, falling back to JS: ${(err as Error).message}`);
125
+ ctx.nativeDb = undefined;
126
+ }
127
+ // Always run JS initSchema so better-sqlite3 sees the schema —
128
+ // nativeDb is closed during pipeline stages and reopened for analyses.
129
+ initSchema(ctx.db);
130
+ } else {
131
+ initSchema(ctx.db);
132
+ }
95
133
 
96
134
  ctx.config = loadConfig(ctx.rootDir);
97
135
  ctx.incremental =
@@ -134,6 +172,26 @@ function formatTimingResult(ctx: PipelineContext): BuildResult {
134
172
  // ── Pipeline stages execution ───────────────────────────────────────────
135
173
 
136
174
  async function runPipelineStages(ctx: PipelineContext): Promise<void> {
175
+ // Prevent dual-connection WAL corruption during pipeline stages: when both
176
+ // better-sqlite3 (ctx.db) and rusqlite (ctx.nativeDb) are open to the same
177
+ // WAL-mode file, native writes corrupt the DB. Close nativeDb so stages
178
+ // use JS fallback paths. Reopened before runAnalyses for feature modules
179
+ // that use suspendJsDb/resumeJsDb WAL checkpoint pattern (#696).
180
+ const hadNativeDb = !!ctx.nativeDb;
181
+ if (ctx.db && ctx.nativeDb) {
182
+ try {
183
+ ctx.nativeDb.close();
184
+ } catch {
185
+ /* ignore close errors */
186
+ }
187
+ ctx.nativeDb = undefined;
188
+ // Also clear stale reference in engineOpts to prevent stages from
189
+ // calling methods on the closed NativeDatabase.
190
+ if (ctx.engineOpts?.nativeDb) {
191
+ ctx.engineOpts.nativeDb = undefined;
192
+ }
193
+ }
194
+
137
195
  await collectFiles(ctx);
138
196
  await detectChanges(ctx);
139
197
 
@@ -144,7 +202,39 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
144
202
  await resolveImports(ctx);
145
203
  await buildEdges(ctx);
146
204
  await buildStructure(ctx);
205
+
206
+ // Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow)
207
+ // which use suspendJsDb/resumeJsDb WAL checkpoint before native writes.
208
+ if (hadNativeDb) {
209
+ const native = loadNative();
210
+ if (native?.NativeDatabase) {
211
+ try {
212
+ ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
213
+ if (ctx.engineOpts) {
214
+ ctx.engineOpts.nativeDb = ctx.nativeDb;
215
+ }
216
+ } catch {
217
+ ctx.nativeDb = undefined;
218
+ if (ctx.engineOpts) {
219
+ ctx.engineOpts.nativeDb = undefined;
220
+ }
221
+ }
222
+ }
223
+ }
224
+
147
225
  await runAnalyses(ctx);
226
+
227
+ // Close nativeDb after analyses — finalize uses JS paths for setBuildMeta
228
+ // and closeDbPair handles cleanup. Avoids dual-connection during finalize.
229
+ if (ctx.nativeDb) {
230
+ try {
231
+ ctx.nativeDb.close();
232
+ } catch {
233
+ /* ignore close errors */
234
+ }
235
+ ctx.nativeDb = undefined;
236
+ }
237
+
148
238
  await finalize(ctx);
149
239
  }
150
240
 
@@ -168,7 +258,9 @@ export async function buildGraph(
168
258
  setupPipeline(ctx);
169
259
  await runPipelineStages(ctx);
170
260
  } catch (err) {
171
- if (!ctx.earlyExit && ctx.db) closeDb(ctx.db);
261
+ if (!ctx.earlyExit && ctx.db) {
262
+ closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
263
+ }
172
264
  throw err;
173
265
  }
174
266
 
@@ -7,6 +7,7 @@
7
7
  import path from 'node:path';
8
8
  import { performance } from 'node:perf_hooks';
9
9
  import { getNodeId } from '../../../../db/index.js';
10
+ import { debug } from '../../../../infrastructure/logger.js';
10
11
  import { loadNative } from '../../../../infrastructure/native.js';
11
12
  import type {
12
13
  BetterSqlite3Database,
@@ -21,6 +22,7 @@ import type {
21
22
  import { computeConfidence } from '../../resolve.js';
22
23
  import type { PipelineContext } from '../context.js';
23
24
  import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
25
+
24
26
  import { getResolved, isBarrelFile, resolveBarrelExport } from './resolve-imports.js';
25
27
 
26
28
  // ── Local types ──────────────────────────────────────────────────────────
@@ -60,12 +62,6 @@ interface NativeEdge {
60
62
  dynamic: number;
61
63
  }
62
64
 
63
- /** TypeMap entry used in receiver supplement (normalized from native format). */
64
- interface NormalizedTypeEntry {
65
- type: string;
66
- confidence: number;
67
- }
68
-
69
65
  // ── Node lookup setup ───────────────────────────────────────────────────
70
66
 
71
67
  function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
@@ -209,14 +205,6 @@ function buildCallEdgesNative(
209
205
  for (const e of nativeEdges) {
210
206
  allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
211
207
  }
212
-
213
- // Older native binaries (< 3.2.0) don't emit receiver or type-resolved method-call
214
- // edges. Supplement them on the JS side if the native binary missed them.
215
- // TODO: Remove once all published native binaries handle receivers (>= 3.2.0)
216
- const hasReceiver = nativeEdges.some((e) => e.kind === 'receiver');
217
- if (!hasReceiver) {
218
- supplementReceiverEdges(ctx, nativeFiles, getNodeIdStmt, allEdgeRows);
219
- }
220
208
  }
221
209
 
222
210
  function buildImportedNamesForNative(
@@ -241,58 +229,6 @@ function buildImportedNamesForNative(
241
229
  return importedNames;
242
230
  }
243
231
 
244
- // ── Receiver edge supplement for older native binaries ──────────────────
245
-
246
- function supplementReceiverEdges(
247
- ctx: PipelineContext,
248
- nativeFiles: NativeFileEntry[],
249
- getNodeIdStmt: NodeIdStmt,
250
- allEdgeRows: EdgeRowTuple[],
251
- ): void {
252
- const seenCallEdges = new Set<string>();
253
- // Collect existing edges to avoid duplicates
254
- for (const row of allEdgeRows) {
255
- seenCallEdges.add(`${row[0]}|${row[1]}|${row[2]}`);
256
- }
257
-
258
- for (const nf of nativeFiles) {
259
- const relPath = nf.file;
260
- const typeMap = new Map<string, NormalizedTypeEntry>(
261
- nf.typeMap.map((t) => [t.name, { type: t.typeName, confidence: t.confidence ?? 0.9 }]),
262
- );
263
- const fileNodeRow = { id: nf.fileNodeId };
264
-
265
- for (const call of nf.calls) {
266
- if (!call.receiver || BUILTIN_RECEIVERS.has(call.receiver)) continue;
267
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super')
268
- continue;
269
-
270
- const caller = findCaller(call, nf.definitions, relPath, getNodeIdStmt, fileNodeRow);
271
-
272
- // Receiver edge: caller → receiver type node
273
- buildReceiverEdge(ctx, call, caller, relPath, seenCallEdges, allEdgeRows, typeMap);
274
-
275
- // Type-resolved method call: caller → Type.method
276
- const typeEntry = typeMap.get(call.receiver);
277
- const typeName = typeEntry ? typeEntry.type : null;
278
- if (typeName) {
279
- const qualifiedName = `${typeName}.${call.name}`;
280
- const targets = (ctx.nodesByName.get(qualifiedName) || []).filter(
281
- (n) => n.kind === 'method',
282
- );
283
- for (const t of targets) {
284
- const key = `${caller.id}|${t.id}|calls`;
285
- if (t.id !== caller.id && !seenCallEdges.has(key)) {
286
- seenCallEdges.add(key);
287
- const confidence = computeConfidence(relPath, t.file, null);
288
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0]);
289
- }
290
- }
291
- }
292
- }
293
- }
294
- }
295
-
296
232
  // ── Call edges (JS fallback) ────────────────────────────────────────────
297
233
 
298
234
  function buildCallEdgesJS(
@@ -494,7 +430,7 @@ function buildReceiverEdge(
494
430
  relPath: string,
495
431
  seenCallEdges: Set<string>,
496
432
  allEdgeRows: EdgeRowTuple[],
497
- typeMap: Map<string, TypeMapEntry | NormalizedTypeEntry | string>,
433
+ typeMap: Map<string, TypeMapEntry | string>,
498
434
  ): void {
499
435
  const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
500
436
  const typeEntry = typeMap?.get(call.receiver!);
@@ -561,22 +497,94 @@ function buildClassHierarchyEdges(
561
497
 
562
498
  // ── Main entry point ────────────────────────────────────────────────────
563
499
 
500
+ /**
501
+ * For small incremental builds (≤5 changed files on a large codebase), scope
502
+ * the node loading query to only files that are relevant: changed files +
503
+ * their import targets. Falls back to loading ALL nodes for full builds or
504
+ * larger incremental changes.
505
+ */
506
+ function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
507
+ const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
508
+ const nodeKindFilter = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
509
+
510
+ // Gate: only scope for small incremental on large codebases
511
+ if (!isFullBuild && fileSymbols.size <= 5) {
512
+ const existingFileCount = (
513
+ db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get() as { c: number }
514
+ ).c;
515
+ if (existingFileCount > 20) {
516
+ // Collect relevant files: changed files + their import targets
517
+ const relevantFiles = new Set<string>(fileSymbols.keys());
518
+ if (batchResolved) {
519
+ for (const resolvedPath of batchResolved.values()) {
520
+ relevantFiles.add(resolvedPath);
521
+ }
522
+ }
523
+ // Also add barrel-only files
524
+ for (const barrelPath of ctx.barrelOnlyFiles) {
525
+ relevantFiles.add(barrelPath);
526
+ }
527
+
528
+ const placeholders = [...relevantFiles].map(() => '?').join(',');
529
+ const rows = db
530
+ .prepare(
531
+ `SELECT id, name, kind, file, line FROM nodes WHERE ${nodeKindFilter} AND file IN (${placeholders})`,
532
+ )
533
+ .all(...relevantFiles) as QueryNodeRow[];
534
+ return { rows, scoped: true };
535
+ }
536
+ }
537
+
538
+ const rows = db
539
+ .prepare(`SELECT id, name, kind, file, line FROM nodes WHERE ${nodeKindFilter}`)
540
+ .all() as QueryNodeRow[];
541
+ return { rows, scoped: false };
542
+ }
543
+
544
+ /**
545
+ * For scoped node loading, patch nodesByName.get with a lazy SQL fallback
546
+ * so global name-only lookups (resolveByMethodOrGlobal)
547
+ * can still find nodes outside the scoped set.
548
+ */
549
+ function addLazyFallback(ctx: PipelineContext, scopedLoad: boolean): void {
550
+ if (!scopedLoad) return;
551
+ const { db } = ctx;
552
+ const fallbackStmt = db.prepare(
553
+ `SELECT id, name, kind, file, line FROM nodes WHERE name = ? AND kind != 'file'`,
554
+ );
555
+ const originalGet = ctx.nodesByName.get.bind(ctx.nodesByName);
556
+ ctx.nodesByName.get = (name: string) => {
557
+ const result = originalGet(name);
558
+ if (result !== undefined) return result;
559
+ const rows = fallbackStmt.all(name) as unknown as NodeRow[];
560
+ if (rows.length > 0) {
561
+ ctx.nodesByName.set(name, rows);
562
+ return rows;
563
+ }
564
+ return undefined;
565
+ };
566
+ }
567
+
564
568
  export async function buildEdges(ctx: PipelineContext): Promise<void> {
565
569
  const { db, engineName } = ctx;
566
570
 
567
571
  const getNodeIdStmt = makeGetNodeIdStmt(db);
568
572
 
569
- const allNodes = db
570
- .prepare(
571
- `SELECT id, name, kind, file, line FROM nodes WHERE kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`,
572
- )
573
- .all() as QueryNodeRow[];
574
- setupNodeLookups(ctx, allNodes);
573
+ const { rows: allNodesBefore, scoped: scopedLoad } = loadNodes(ctx);
574
+ setupNodeLookups(ctx, allNodesBefore);
575
+ addLazyFallback(ctx, scopedLoad);
575
576
 
576
577
  const t0 = performance.now();
577
- const buildEdgesTx = db.transaction(() => {
578
- // Delete stale outgoing edges for barrel-only files inside the transaction
579
- // so that deletion and re-creation are atomic (no edge loss on mid-build crash).
578
+ const native = engineName === 'native' ? loadNative() : null;
579
+
580
+ // Phase 1: Compute edges inside a better-sqlite3 transaction.
581
+ // Barrel-edge deletion lives here so that the JS path (which also inserts
582
+ // edges in this transaction) keeps deletion + insertion atomic.
583
+ // When using the native rusqlite path, insertion happens in Phase 2 on a
584
+ // separate connection — a crash between Phase 1 and Phase 2 would leave
585
+ // barrel edges missing until the next incremental rebuild re-creates them.
586
+ const allEdgeRows: EdgeRowTuple[] = [];
587
+ const computeEdgesTx = db.transaction(() => {
580
588
  if (ctx.barrelOnlyFiles.size > 0) {
581
589
  const deleteOutgoingEdges = db.prepare(
582
590
  'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
@@ -586,19 +594,44 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
586
594
  }
587
595
  }
588
596
 
589
- const allEdgeRows: EdgeRowTuple[] = [];
590
-
591
597
  buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
592
598
 
593
- const native = engineName === 'native' ? loadNative() : null;
594
- if (native?.buildCallEdges) {
595
- buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native);
599
+ // Skip native call-edge path for small incremental builds (≤3 files):
600
+ // napi-rs marshaling overhead for allNodes exceeds computation savings.
601
+ const useNativeCallEdges =
602
+ native?.buildCallEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
603
+ if (useNativeCallEdges) {
604
+ buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native!);
596
605
  } else {
597
606
  buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows);
598
607
  }
599
608
 
600
- batchInsertEdges(db, allEdgeRows);
609
+ // When using native edge insert, skip JS insert here — do it after tx commits.
610
+ // Otherwise insert edges within this transaction for atomicity.
611
+ const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
612
+ if (!useNativeEdgeInsert) {
613
+ batchInsertEdges(db, allEdgeRows);
614
+ }
601
615
  });
602
- buildEdgesTx();
616
+ computeEdgesTx();
617
+
618
+ // Phase 2: Native rusqlite bulk insert (outside better-sqlite3 transaction
619
+ // to avoid SQLITE_BUSY contention). Uses NativeDatabase persistent connection.
620
+ // Standalone napi functions were removed in 6.17.
621
+ if (ctx.engineName === 'native' && ctx.nativeDb?.bulkInsertEdges && allEdgeRows.length > 0) {
622
+ const nativeEdges = allEdgeRows.map((r) => ({
623
+ sourceId: r[0],
624
+ targetId: r[1],
625
+ kind: r[2],
626
+ confidence: r[3],
627
+ dynamic: r[4],
628
+ }));
629
+ const ok = ctx.nativeDb.bulkInsertEdges(nativeEdges);
630
+ if (!ok) {
631
+ debug('Native bulkInsertEdges failed — falling back to JS batchInsertEdges');
632
+ batchInsertEdges(ctx.db, allEdgeRows);
633
+ }
634
+ }
635
+
603
636
  ctx.timing.edgesMs = performance.now() - t0;
604
637
  }
@@ -39,8 +39,15 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
39
39
  // loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
40
40
  // Gate: ≤5 changed files AND significantly more existing files (>20) to
41
41
  // avoid triggering on small test fixtures where directory metrics matter.
42
+ const useNativeReads = ctx.engineName === 'native' && !!ctx.nativeDb;
42
43
  const existingFileCount = !isFullBuild
43
- ? (db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get() as { c: number }).c
44
+ ? (
45
+ (useNativeReads
46
+ ? ctx.nativeDb!.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
47
+ : db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get()) as {
48
+ c: number;
49
+ }
50
+ ).c
44
51
  : 0;
45
52
  const useSmallIncrementalFastPath =
46
53
  !isFullBuild &&
@@ -86,13 +93,44 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
86
93
  // Classify node roles (incremental: only reclassify changed files' nodes)
87
94
  const t1 = performance.now();
88
95
  try {
89
- const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
90
- classifyNodeRoles: (
91
- db: PipelineContext['db'],
92
- changedFiles?: string[] | null,
93
- ) => Record<string, number>;
94
- };
95
- const roleSummary = classifyNodeRoles(db, changedFileList);
96
+ let roleSummary: Record<string, number> | null = null;
97
+
98
+ // Use NativeDatabase persistent connection (Phase 6.15+).
99
+ // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
100
+ // Note: classifyRoles* both read (fan-in/fan-out) and write (UPDATE nodes SET role).
101
+ if (useNativeReads && ctx.nativeDb?.classifyRolesFull) {
102
+ const nativeResult =
103
+ changedFileList && changedFileList.length > 0
104
+ ? ctx.nativeDb.classifyRolesIncremental(changedFileList)
105
+ : ctx.nativeDb.classifyRolesFull();
106
+ if (nativeResult) {
107
+ roleSummary = {
108
+ entry: nativeResult.entry,
109
+ core: nativeResult.core,
110
+ utility: nativeResult.utility,
111
+ adapter: nativeResult.adapter,
112
+ dead: nativeResult.dead,
113
+ 'dead-leaf': nativeResult.deadLeaf,
114
+ 'dead-entry': nativeResult.deadEntry,
115
+ 'dead-ffi': nativeResult.deadFfi,
116
+ 'dead-unresolved': nativeResult.deadUnresolved,
117
+ 'test-only': nativeResult.testOnly,
118
+ leaf: nativeResult.leaf,
119
+ };
120
+ }
121
+ }
122
+
123
+ // Fall back to JS path
124
+ if (!roleSummary) {
125
+ const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
126
+ classifyNodeRoles: (
127
+ db: PipelineContext['db'],
128
+ changedFiles?: string[] | null,
129
+ ) => Record<string, number>;
130
+ };
131
+ roleSummary = classifyNodeRoles(ctx.db, changedFileList);
132
+ }
133
+
96
134
  debug(
97
135
  `Roles${changedFileList ? ` (incremental, ${changedFileList.length} files)` : ''}: ${Object.entries(
98
136
  roleSummary,