@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
@@ -275,16 +275,161 @@ function allCfgNative(fileSymbols: Map<string, FileSymbols>): boolean {
275
275
  return hasCfgFile;
276
276
  }
277
277
 
278
+ /** Persist native CFG data for a single file (fast path — no tree/visitor needed). */
279
+ function persistNativeFileCfg(
280
+ db: BetterSqlite3Database,
281
+ symbols: FileSymbols,
282
+ relPath: string,
283
+ insertBlock: ReturnType<BetterSqlite3Database['prepare']>,
284
+ insertEdge: ReturnType<BetterSqlite3Database['prepare']>,
285
+ ): number {
286
+ let count = 0;
287
+ for (const def of symbols.definitions) {
288
+ if (def.kind !== 'function' && def.kind !== 'method') continue;
289
+ if (!def.line) continue;
290
+
291
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
292
+ if (!nodeId) continue;
293
+
294
+ deleteCfgForNode(db, nodeId);
295
+ if (!def.cfg?.blocks?.length) continue;
296
+
297
+ persistCfg(
298
+ def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] },
299
+ nodeId,
300
+ insertBlock,
301
+ insertEdge,
302
+ );
303
+ count++;
304
+ }
305
+ return count;
306
+ }
307
+
308
+ /** Resolve CFG for a definition from native data or visitor results. */
309
+ function resolveCfgForDef(
310
+ def: Definition,
311
+ visitorCfgByLine: Map<number, VisitorCfgResult[]> | null,
312
+ ): { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] } | null {
313
+ if (def.cfg?.blocks?.length) {
314
+ return def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
315
+ }
316
+ if (!visitorCfgByLine) return null;
317
+ const candidates = visitorCfgByLine.get(def.line);
318
+ if (!candidates) return null;
319
+ const r =
320
+ candidates.length === 1
321
+ ? candidates[0]
322
+ : (candidates.find((c) => {
323
+ const n = c.funcNode.childForFieldName?.('name');
324
+ return n && n.text === def.name;
325
+ }) ?? candidates[0]);
326
+ return r ? { blocks: r.blocks, edges: r.edges } : null;
327
+ }
328
+
329
+ /** Persist CFG data for a single file using visitor/native hybrid path. */
330
+ function persistVisitorFileCfg(
331
+ db: BetterSqlite3Database,
332
+ symbols: FileSymbols,
333
+ relPath: string,
334
+ rootDir: string,
335
+ extToLang: Map<string, string>,
336
+ parsers: unknown,
337
+ getParserFn: unknown,
338
+ insertBlock: ReturnType<BetterSqlite3Database['prepare']>,
339
+ insertEdge: ReturnType<BetterSqlite3Database['prepare']>,
340
+ ): number {
341
+ const treeLang = getTreeAndLang(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
342
+ if (!treeLang) return 0;
343
+ const { tree, langId } = treeLang;
344
+
345
+ const cfgRules = CFG_RULES.get(langId);
346
+ if (!cfgRules) return 0;
347
+
348
+ const visitorCfgByLine = buildVisitorCfgMap(tree, cfgRules, symbols, langId);
349
+ let count = 0;
350
+
351
+ for (const def of symbols.definitions) {
352
+ if (def.kind !== 'function' && def.kind !== 'method') continue;
353
+ if (!def.line) continue;
354
+
355
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
356
+ if (!nodeId) continue;
357
+
358
+ const cfg = resolveCfgForDef(def, visitorCfgByLine);
359
+ deleteCfgForNode(db, nodeId);
360
+ if (!cfg || cfg.blocks.length === 0) continue;
361
+
362
+ persistCfg(cfg, nodeId, insertBlock, insertEdge);
363
+ count++;
364
+ }
365
+ return count;
366
+ }
367
+
278
368
  export async function buildCFGData(
279
369
  db: BetterSqlite3Database,
280
370
  fileSymbols: Map<string, FileSymbols>,
281
371
  rootDir: string,
282
- _engineOpts?: unknown,
372
+ engineOpts?: {
373
+ nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
374
+ suspendJsDb?: () => void;
375
+ resumeJsDb?: () => void;
376
+ },
283
377
  ): Promise<void> {
284
378
  // Fast path: when all function/method defs already have native CFG data,
285
379
  // skip WASM parser init, tree parsing, and JS visitor entirely — just persist.
286
380
  const allNative = allCfgNative(fileSymbols);
287
381
 
382
+ // ── Native bulk-insert fast path ──────────────────────────────────────
383
+ const nativeDb = engineOpts?.nativeDb;
384
+ if (allNative && nativeDb?.bulkInsertCfg) {
385
+ const entries: Array<Record<string, unknown>> = [];
386
+
387
+ for (const [relPath, symbols] of fileSymbols) {
388
+ const ext = path.extname(relPath).toLowerCase();
389
+ if (!CFG_EXTENSIONS.has(ext)) continue;
390
+
391
+ for (const def of symbols.definitions) {
392
+ if (def.kind !== 'function' && def.kind !== 'method') continue;
393
+ if (!def.line) continue;
394
+
395
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
396
+ if (!nodeId) continue;
397
+
398
+ deleteCfgForNode(db, nodeId);
399
+ if (!def.cfg?.blocks?.length) continue;
400
+
401
+ const cfg = def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
402
+ entries.push({
403
+ nodeId,
404
+ blocks: cfg.blocks.map((b) => ({
405
+ index: b.index,
406
+ blockType: b.type,
407
+ startLine: b.startLine ?? null,
408
+ endLine: b.endLine ?? null,
409
+ label: b.label ?? null,
410
+ })),
411
+ edges: cfg.edges.map((e) => ({
412
+ sourceIndex: e.sourceIndex,
413
+ targetIndex: e.targetIndex,
414
+ kind: e.kind,
415
+ })),
416
+ });
417
+ }
418
+ }
419
+
420
+ if (entries.length > 0) {
421
+ let inserted: number;
422
+ try {
423
+ engineOpts?.suspendJsDb?.();
424
+ inserted = nativeDb.bulkInsertCfg(entries);
425
+ } finally {
426
+ engineOpts?.resumeJsDb?.();
427
+ }
428
+ info(`CFG (native bulk): ${inserted} blocks across ${entries.length} functions`);
429
+ }
430
+ return;
431
+ }
432
+
288
433
  const extToLang = buildExtToLangMap();
289
434
  let parsers: unknown = null;
290
435
  let getParserFn: unknown = null;
@@ -308,74 +453,22 @@ export async function buildCFGData(
308
453
  const ext = path.extname(relPath).toLowerCase();
309
454
  if (!CFG_EXTENSIONS.has(ext)) continue;
310
455
 
311
- // Native fast path: skip tree/visitor setup when all CFG is pre-computed.
312
- // Only apply to files without _tree — files with _tree were WASM-parsed
313
- // and need the slow path (visitor) to compute CFG.
314
456
  if (allNative && !symbols._tree) {
315
- for (const def of symbols.definitions) {
316
- if (def.kind !== 'function' && def.kind !== 'method') continue;
317
- if (!def.line) continue;
318
-
319
- const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
320
- if (!nodeId) continue;
321
-
322
- // Always delete stale CFG rows (handles body-removed case)
323
- deleteCfgForNode(db, nodeId);
324
- if (!def.cfg?.blocks?.length) continue;
325
-
326
- persistCfg(
327
- def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] },
328
- nodeId,
329
- insertBlock,
330
- insertEdge,
331
- );
332
- analyzed++;
333
- }
457
+ analyzed += persistNativeFileCfg(db, symbols, relPath, insertBlock, insertEdge);
334
458
  continue;
335
459
  }
336
460
 
337
- // When allNative=true, parsers/getParserFn are null. This is safe because
338
- // _tree files use symbols._tree directly in getTreeAndLang (the parser
339
- // code path is never reached). Non-_tree files are handled by the fast path above.
340
- const treeLang = getTreeAndLang(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
341
- if (!treeLang) continue;
342
- const { tree, langId } = treeLang;
343
-
344
- const cfgRules = CFG_RULES.get(langId);
345
- if (!cfgRules) continue;
346
-
347
- const visitorCfgByLine = buildVisitorCfgMap(tree, cfgRules, symbols, langId);
348
-
349
- for (const def of symbols.definitions) {
350
- if (def.kind !== 'function' && def.kind !== 'method') continue;
351
- if (!def.line) continue;
352
-
353
- const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
354
- if (!nodeId) continue;
355
-
356
- let cfg: { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] } | null = null;
357
- if (def.cfg?.blocks?.length) {
358
- cfg = def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
359
- } else if (visitorCfgByLine) {
360
- const candidates = visitorCfgByLine.get(def.line);
361
- const r = !candidates
362
- ? undefined
363
- : candidates.length === 1
364
- ? candidates[0]
365
- : (candidates.find((c) => {
366
- const n = c.funcNode.childForFieldName?.('name');
367
- return n && n.text === def.name;
368
- }) ?? candidates[0]);
369
- if (r) cfg = { blocks: r.blocks, edges: r.edges };
370
- }
371
-
372
- // Always purge stale rows (handles body-removed case)
373
- deleteCfgForNode(db, nodeId);
374
- if (!cfg || cfg.blocks.length === 0) continue;
375
-
376
- persistCfg(cfg, nodeId, insertBlock, insertEdge);
377
- analyzed++;
378
- }
461
+ analyzed += persistVisitorFileCfg(
462
+ db,
463
+ symbols,
464
+ relPath,
465
+ rootDir,
466
+ extToLang,
467
+ parsers,
468
+ getParserFn,
469
+ insertBlock,
470
+ insertEdge,
471
+ );
379
472
  }
380
473
  });
381
474
 
@@ -291,6 +291,85 @@ interface CheckOpts {
291
291
  config?: CodegraphConfig;
292
292
  }
293
293
 
294
+ /** Walk up from repoRoot to find the nearest .git directory. */
295
+ function findGitRoot(repoRoot: string): string | null {
296
+ let dir = repoRoot;
297
+ while (dir) {
298
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
299
+ const parent = path.dirname(dir);
300
+ if (parent === dir) break;
301
+ dir = parent;
302
+ }
303
+ return null;
304
+ }
305
+
306
+ /** Run git diff and return the raw output string. */
307
+ function getGitDiff(repoRoot: string, opts: { staged?: boolean; ref?: string }): string {
308
+ const args = opts.staged
309
+ ? ['diff', '--cached', '--unified=0', '--no-color']
310
+ : ['diff', opts.ref || 'HEAD', '--unified=0', '--no-color'];
311
+ return execFileSync('git', args, {
312
+ cwd: repoRoot,
313
+ encoding: 'utf-8',
314
+ maxBuffer: 10 * 1024 * 1024,
315
+ stdio: ['pipe', 'pipe', 'pipe'],
316
+ });
317
+ }
318
+
319
+ /** Resolve which check predicates are enabled from opts + config. */
320
+ function resolveCheckFlags(opts: CheckOpts, config: CodegraphConfig) {
321
+ const checkConfig = config.check || ({} as CodegraphConfig['check']);
322
+ return {
323
+ enableCycles: opts.cycles ?? checkConfig.cycles ?? true,
324
+ enableSignatures: opts.signatures ?? checkConfig.signatures ?? true,
325
+ enableBoundaries: opts.boundaries ?? checkConfig.boundaries ?? true,
326
+ blastRadiusThreshold: opts.blastRadius ?? checkConfig.blastRadius ?? null,
327
+ };
328
+ }
329
+
330
+ /** Run all enabled check predicates and return the results. */
331
+ function runPredicates(
332
+ db: BetterSqlite3Database,
333
+ diff: ParsedDiff,
334
+ flags: ReturnType<typeof resolveCheckFlags>,
335
+ repoRoot: string,
336
+ noTests: boolean,
337
+ maxDepth: number,
338
+ ): PredicateResult[] {
339
+ const changedFiles = new Set(diff.changedRanges.keys());
340
+ const predicates: PredicateResult[] = [];
341
+
342
+ if (flags.enableCycles) {
343
+ predicates.push({ name: 'cycles', ...checkNoNewCycles(db, changedFiles, noTests) });
344
+ }
345
+ if (flags.blastRadiusThreshold != null) {
346
+ predicates.push({
347
+ name: 'blast-radius',
348
+ ...checkMaxBlastRadius(db, diff.changedRanges, flags.blastRadiusThreshold, noTests, maxDepth),
349
+ });
350
+ }
351
+ if (flags.enableSignatures) {
352
+ predicates.push({
353
+ name: 'signatures',
354
+ ...checkNoSignatureChanges(db, diff.oldRanges, noTests),
355
+ });
356
+ }
357
+ if (flags.enableBoundaries) {
358
+ predicates.push({
359
+ name: 'boundaries',
360
+ ...checkNoBoundaryViolations(db, changedFiles, repoRoot, noTests),
361
+ });
362
+ }
363
+
364
+ return predicates;
365
+ }
366
+
367
+ const EMPTY_CHECK: CheckResult = {
368
+ predicates: [],
369
+ summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
370
+ passed: true,
371
+ };
372
+
294
373
  export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}): CheckResult {
295
374
  const db = openReadonlyOrFail(customDbPath);
296
375
 
@@ -301,89 +380,26 @@ export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}
301
380
  const maxDepth = opts.depth || 3;
302
381
 
303
382
  const config = opts.config || loadConfig(repoRoot);
304
- const checkConfig = config.check || ({} as CodegraphConfig['check']);
305
-
306
- const enableCycles = opts.cycles ?? checkConfig.cycles ?? true;
307
- const enableSignatures = opts.signatures ?? checkConfig.signatures ?? true;
308
- const enableBoundaries = opts.boundaries ?? checkConfig.boundaries ?? true;
309
- const blastRadiusThreshold = opts.blastRadius ?? checkConfig.blastRadius ?? null;
310
-
311
- let checkDir = repoRoot;
312
- let isGitRepo = false;
313
- while (checkDir) {
314
- if (fs.existsSync(path.join(checkDir, '.git'))) {
315
- isGitRepo = true;
316
- break;
317
- }
318
- const parent = path.dirname(checkDir);
319
- if (parent === checkDir) break;
320
- checkDir = parent;
321
- }
322
- if (!isGitRepo) {
383
+ const flags = resolveCheckFlags(opts, config);
384
+
385
+ const gitRoot = findGitRoot(repoRoot);
386
+ if (!gitRoot) {
323
387
  return { error: `Not a git repository: ${repoRoot}` };
324
388
  }
325
389
 
326
390
  let diffOutput: string;
327
391
  try {
328
- const args = opts.staged
329
- ? ['diff', '--cached', '--unified=0', '--no-color']
330
- : ['diff', opts.ref || 'HEAD', '--unified=0', '--no-color'];
331
- diffOutput = execFileSync('git', args, {
332
- cwd: repoRoot,
333
- encoding: 'utf-8',
334
- maxBuffer: 10 * 1024 * 1024,
335
- stdio: ['pipe', 'pipe', 'pipe'],
336
- });
392
+ diffOutput = getGitDiff(repoRoot, opts);
337
393
  } catch (e) {
338
394
  return { error: `Failed to run git diff: ${(e as Error).message}` };
339
395
  }
340
396
 
341
- if (!diffOutput.trim()) {
342
- return {
343
- predicates: [],
344
- summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
345
- passed: true,
346
- };
347
- }
348
-
349
- const { changedRanges, oldRanges, newFiles } = parseDiffOutput(diffOutput);
350
- if (changedRanges.size === 0) {
351
- return {
352
- predicates: [],
353
- summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
354
- passed: true,
355
- };
356
- }
357
-
358
- const changedFiles = new Set(changedRanges.keys());
397
+ if (!diffOutput.trim()) return EMPTY_CHECK;
359
398
 
360
- const predicates: PredicateResult[] = [];
399
+ const diff = parseDiffOutput(diffOutput);
400
+ if (diff.changedRanges.size === 0) return EMPTY_CHECK;
361
401
 
362
- if (enableCycles) {
363
- const result = checkNoNewCycles(db, changedFiles, noTests);
364
- predicates.push({ name: 'cycles', ...result });
365
- }
366
-
367
- if (blastRadiusThreshold != null) {
368
- const result = checkMaxBlastRadius(
369
- db,
370
- changedRanges,
371
- blastRadiusThreshold,
372
- noTests,
373
- maxDepth,
374
- );
375
- predicates.push({ name: 'blast-radius', ...result });
376
- }
377
-
378
- if (enableSignatures) {
379
- const result = checkNoSignatureChanges(db, oldRanges, noTests);
380
- predicates.push({ name: 'signatures', ...result });
381
- }
382
-
383
- if (enableBoundaries) {
384
- const result = checkNoBoundaryViolations(db, changedFiles, repoRoot, noTests);
385
- predicates.push({ name: 'boundaries', ...result });
386
- }
402
+ const predicates = runPredicates(db, diff, flags, repoRoot, noTests, maxDepth);
387
403
 
388
404
  const passedCount = predicates.filter((p) => p.passed).length;
389
405
  const failedCount = predicates.length - passedCount;
@@ -394,8 +410,8 @@ export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}
394
410
  total: predicates.length,
395
411
  passed: passedCount,
396
412
  failed: failedCount,
397
- changedFiles: changedFiles.size,
398
- newFiles: newFiles.size,
413
+ changedFiles: diff.changedRanges.size,
414
+ newFiles: diff.newFiles.size,
399
415
  },
400
416
  passed: failedCount === 0,
401
417
  };