@optave/codegraph 3.5.0 → 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 (310) hide show
  1. package/README.md +35 -14
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +164 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/go.d.ts.map +1 -1
  103. package/dist/extractors/go.js +126 -130
  104. package/dist/extractors/go.js.map +1 -1
  105. package/dist/extractors/hcl.js +6 -6
  106. package/dist/extractors/hcl.js.map +1 -1
  107. package/dist/extractors/helpers.d.ts +32 -1
  108. package/dist/extractors/helpers.d.ts.map +1 -1
  109. package/dist/extractors/helpers.js +74 -0
  110. package/dist/extractors/helpers.js.map +1 -1
  111. package/dist/extractors/index.d.ts +6 -0
  112. package/dist/extractors/index.d.ts.map +1 -1
  113. package/dist/extractors/index.js +6 -0
  114. package/dist/extractors/index.js.map +1 -1
  115. package/dist/extractors/java.d.ts.map +1 -1
  116. package/dist/extractors/java.js +32 -47
  117. package/dist/extractors/java.js.map +1 -1
  118. package/dist/extractors/javascript.d.ts.map +1 -1
  119. package/dist/extractors/javascript.js +306 -292
  120. package/dist/extractors/javascript.js.map +1 -1
  121. package/dist/extractors/kotlin.d.ts +6 -0
  122. package/dist/extractors/kotlin.d.ts.map +1 -0
  123. package/dist/extractors/kotlin.js +275 -0
  124. package/dist/extractors/kotlin.js.map +1 -0
  125. package/dist/extractors/php.d.ts.map +1 -1
  126. package/dist/extractors/php.js +39 -44
  127. package/dist/extractors/php.js.map +1 -1
  128. package/dist/extractors/python.d.ts.map +1 -1
  129. package/dist/extractors/python.js +75 -93
  130. package/dist/extractors/python.js.map +1 -1
  131. package/dist/extractors/ruby.js +6 -13
  132. package/dist/extractors/ruby.js.map +1 -1
  133. package/dist/extractors/rust.d.ts.map +1 -1
  134. package/dist/extractors/rust.js +58 -83
  135. package/dist/extractors/rust.js.map +1 -1
  136. package/dist/extractors/scala.d.ts +6 -0
  137. package/dist/extractors/scala.d.ts.map +1 -0
  138. package/dist/extractors/scala.js +269 -0
  139. package/dist/extractors/scala.js.map +1 -0
  140. package/dist/extractors/swift.d.ts +6 -0
  141. package/dist/extractors/swift.d.ts.map +1 -0
  142. package/dist/extractors/swift.js +275 -0
  143. package/dist/extractors/swift.js.map +1 -0
  144. package/dist/features/ast.d.ts +2 -0
  145. package/dist/features/ast.d.ts.map +1 -1
  146. package/dist/features/ast.js +9 -24
  147. package/dist/features/ast.js.map +1 -1
  148. package/dist/features/audit.d.ts.map +1 -1
  149. package/dist/features/audit.js +17 -21
  150. package/dist/features/audit.js.map +1 -1
  151. package/dist/features/branch-compare.d.ts.map +1 -1
  152. package/dist/features/branch-compare.js +47 -3
  153. package/dist/features/branch-compare.js.map +1 -1
  154. package/dist/features/cfg.d.ts +7 -1
  155. package/dist/features/cfg.d.ts.map +1 -1
  156. package/dist/features/cfg.js +118 -62
  157. package/dist/features/cfg.js.map +1 -1
  158. package/dist/features/check.d.ts.map +1 -1
  159. package/dist/features/check.js +79 -62
  160. package/dist/features/check.js.map +1 -1
  161. package/dist/features/complexity-query.d.ts.map +1 -1
  162. package/dist/features/complexity-query.js +142 -137
  163. package/dist/features/complexity-query.js.map +1 -1
  164. package/dist/features/complexity.d.ts +7 -1
  165. package/dist/features/complexity.d.ts.map +1 -1
  166. package/dist/features/complexity.js +62 -1
  167. package/dist/features/complexity.js.map +1 -1
  168. package/dist/features/dataflow.d.ts +7 -1
  169. package/dist/features/dataflow.d.ts.map +1 -1
  170. package/dist/features/dataflow.js +356 -188
  171. package/dist/features/dataflow.js.map +1 -1
  172. package/dist/features/graph-enrichment.d.ts.map +1 -1
  173. package/dist/features/graph-enrichment.js +117 -104
  174. package/dist/features/graph-enrichment.js.map +1 -1
  175. package/dist/features/sequence.d.ts.map +1 -1
  176. package/dist/features/sequence.js +25 -4
  177. package/dist/features/sequence.js.map +1 -1
  178. package/dist/features/structure-query.d.ts.map +1 -1
  179. package/dist/features/structure-query.js +29 -4
  180. package/dist/features/structure-query.js.map +1 -1
  181. package/dist/features/structure.d.ts.map +1 -1
  182. package/dist/features/structure.js +35 -15
  183. package/dist/features/structure.js.map +1 -1
  184. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  185. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  186. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  187. package/dist/graph/algorithms/leiden/index.js +43 -28
  188. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  189. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  190. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  191. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  192. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  193. package/dist/graph/algorithms/leiden/partition.js +89 -106
  194. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  195. package/dist/graph/model.d.ts +2 -0
  196. package/dist/graph/model.d.ts.map +1 -1
  197. package/dist/graph/model.js +20 -8
  198. package/dist/graph/model.js.map +1 -1
  199. package/dist/infrastructure/config.d.ts +0 -8
  200. package/dist/infrastructure/config.d.ts.map +1 -1
  201. package/dist/infrastructure/config.js +73 -62
  202. package/dist/infrastructure/config.js.map +1 -1
  203. package/dist/infrastructure/registry.d.ts +0 -8
  204. package/dist/infrastructure/registry.d.ts.map +1 -1
  205. package/dist/infrastructure/registry.js +12 -14
  206. package/dist/infrastructure/registry.js.map +1 -1
  207. package/dist/mcp/server.d.ts.map +1 -1
  208. package/dist/mcp/server.js +45 -36
  209. package/dist/mcp/server.js.map +1 -1
  210. package/dist/presentation/audit.d.ts.map +1 -1
  211. package/dist/presentation/audit.js +61 -57
  212. package/dist/presentation/audit.js.map +1 -1
  213. package/dist/presentation/branch-compare.d.ts.map +1 -1
  214. package/dist/presentation/branch-compare.js +56 -38
  215. package/dist/presentation/branch-compare.js.map +1 -1
  216. package/dist/presentation/check.d.ts.map +1 -1
  217. package/dist/presentation/check.js +30 -32
  218. package/dist/presentation/check.js.map +1 -1
  219. package/dist/presentation/colors.d.ts.map +1 -1
  220. package/dist/presentation/colors.js +2 -0
  221. package/dist/presentation/colors.js.map +1 -1
  222. package/dist/presentation/complexity.d.ts.map +1 -1
  223. package/dist/presentation/complexity.js +25 -19
  224. package/dist/presentation/complexity.js.map +1 -1
  225. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  226. package/dist/presentation/queries-cli/exports.js +15 -15
  227. package/dist/presentation/queries-cli/exports.js.map +1 -1
  228. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  229. package/dist/presentation/queries-cli/impact.js +29 -19
  230. package/dist/presentation/queries-cli/impact.js.map +1 -1
  231. package/dist/types.d.ts +182 -7
  232. package/dist/types.d.ts.map +1 -1
  233. package/grammars/tree-sitter-bash.wasm +0 -0
  234. package/grammars/tree-sitter-c.wasm +0 -0
  235. package/grammars/tree-sitter-cpp.wasm +0 -0
  236. package/grammars/tree-sitter-kotlin.wasm +0 -0
  237. package/grammars/tree-sitter-scala.wasm +0 -0
  238. package/grammars/tree-sitter-swift.wasm +0 -0
  239. package/package.json +13 -7
  240. package/src/ast-analysis/engine.ts +147 -138
  241. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  242. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  243. package/src/db/connection.ts +90 -59
  244. package/src/db/index.ts +1 -0
  245. package/src/db/migrations.ts +36 -32
  246. package/src/domain/analysis/context.ts +73 -75
  247. package/src/domain/analysis/dependencies.ts +78 -68
  248. package/src/domain/analysis/exports.ts +45 -34
  249. package/src/domain/analysis/fn-impact.ts +67 -64
  250. package/src/domain/analysis/module-map.ts +103 -8
  251. package/src/domain/analysis/query-helpers.ts +35 -0
  252. package/src/domain/graph/builder/helpers.ts +12 -6
  253. package/src/domain/graph/builder/incremental.ts +3 -2
  254. package/src/domain/graph/builder/pipeline.ts +71 -3
  255. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  256. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  257. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  258. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  259. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  260. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  261. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  262. package/src/domain/graph/resolve.ts +34 -46
  263. package/src/domain/graph/watcher.ts +12 -14
  264. package/src/domain/parser.ts +168 -97
  265. package/src/domain/search/search/cli-formatter.ts +121 -94
  266. package/src/extractors/bash.ts +97 -0
  267. package/src/extractors/c.ts +212 -0
  268. package/src/extractors/cpp.ts +298 -0
  269. package/src/extractors/csharp.ts +53 -56
  270. package/src/extractors/go.ts +152 -134
  271. package/src/extractors/hcl.ts +6 -6
  272. package/src/extractors/helpers.ts +93 -1
  273. package/src/extractors/index.ts +6 -0
  274. package/src/extractors/java.ts +43 -48
  275. package/src/extractors/javascript.ts +328 -281
  276. package/src/extractors/kotlin.ts +293 -0
  277. package/src/extractors/php.ts +46 -40
  278. package/src/extractors/python.ts +81 -104
  279. package/src/extractors/ruby.ts +6 -13
  280. package/src/extractors/rust.ts +65 -85
  281. package/src/extractors/scala.ts +285 -0
  282. package/src/extractors/swift.ts +293 -0
  283. package/src/features/ast.ts +10 -25
  284. package/src/features/audit.ts +24 -20
  285. package/src/features/branch-compare.ts +51 -4
  286. package/src/features/cfg.ts +158 -65
  287. package/src/features/check.ts +90 -74
  288. package/src/features/complexity-query.ts +181 -163
  289. package/src/features/complexity.ts +64 -1
  290. package/src/features/dataflow.ts +462 -217
  291. package/src/features/graph-enrichment.ts +161 -117
  292. package/src/features/sequence.ts +27 -4
  293. package/src/features/structure-query.ts +43 -4
  294. package/src/features/structure.ts +50 -22
  295. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  296. package/src/graph/algorithms/leiden/index.ts +67 -28
  297. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  298. package/src/graph/algorithms/leiden/partition.ts +131 -98
  299. package/src/graph/model.ts +19 -7
  300. package/src/infrastructure/config.ts +60 -58
  301. package/src/infrastructure/registry.ts +17 -14
  302. package/src/mcp/server.ts +46 -37
  303. package/src/presentation/audit.ts +72 -67
  304. package/src/presentation/branch-compare.ts +54 -50
  305. package/src/presentation/check.ts +34 -34
  306. package/src/presentation/colors.ts +2 -0
  307. package/src/presentation/complexity.ts +39 -33
  308. package/src/presentation/queries-cli/exports.ts +17 -17
  309. package/src/presentation/queries-cli/impact.ts +30 -22
  310. package/src/types.ts +189 -7
@@ -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
  };