@optave/codegraph 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/README.md +47 -21
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +206 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/dart.d.ts +6 -0
  103. package/dist/extractors/dart.d.ts.map +1 -0
  104. package/dist/extractors/dart.js +277 -0
  105. package/dist/extractors/dart.js.map +1 -0
  106. package/dist/extractors/elixir.d.ts +9 -0
  107. package/dist/extractors/elixir.d.ts.map +1 -0
  108. package/dist/extractors/elixir.js +223 -0
  109. package/dist/extractors/elixir.js.map +1 -0
  110. package/dist/extractors/go.d.ts.map +1 -1
  111. package/dist/extractors/go.js +126 -130
  112. package/dist/extractors/go.js.map +1 -1
  113. package/dist/extractors/haskell.d.ts +8 -0
  114. package/dist/extractors/haskell.d.ts.map +1 -0
  115. package/dist/extractors/haskell.js +217 -0
  116. package/dist/extractors/haskell.js.map +1 -0
  117. package/dist/extractors/hcl.js +6 -6
  118. package/dist/extractors/hcl.js.map +1 -1
  119. package/dist/extractors/helpers.d.ts +32 -1
  120. package/dist/extractors/helpers.d.ts.map +1 -1
  121. package/dist/extractors/helpers.js +74 -0
  122. package/dist/extractors/helpers.js.map +1 -1
  123. package/dist/extractors/index.d.ts +12 -0
  124. package/dist/extractors/index.d.ts.map +1 -1
  125. package/dist/extractors/index.js +12 -0
  126. package/dist/extractors/index.js.map +1 -1
  127. package/dist/extractors/java.d.ts.map +1 -1
  128. package/dist/extractors/java.js +32 -47
  129. package/dist/extractors/java.js.map +1 -1
  130. package/dist/extractors/javascript.d.ts.map +1 -1
  131. package/dist/extractors/javascript.js +306 -292
  132. package/dist/extractors/javascript.js.map +1 -1
  133. package/dist/extractors/kotlin.d.ts +6 -0
  134. package/dist/extractors/kotlin.d.ts.map +1 -0
  135. package/dist/extractors/kotlin.js +275 -0
  136. package/dist/extractors/kotlin.js.map +1 -0
  137. package/dist/extractors/lua.d.ts +6 -0
  138. package/dist/extractors/lua.d.ts.map +1 -0
  139. package/dist/extractors/lua.js +162 -0
  140. package/dist/extractors/lua.js.map +1 -0
  141. package/dist/extractors/ocaml.d.ts +6 -0
  142. package/dist/extractors/ocaml.d.ts.map +1 -0
  143. package/dist/extractors/ocaml.js +236 -0
  144. package/dist/extractors/ocaml.js.map +1 -0
  145. package/dist/extractors/php.d.ts.map +1 -1
  146. package/dist/extractors/php.js +39 -44
  147. package/dist/extractors/php.js.map +1 -1
  148. package/dist/extractors/python.d.ts.map +1 -1
  149. package/dist/extractors/python.js +75 -93
  150. package/dist/extractors/python.js.map +1 -1
  151. package/dist/extractors/ruby.js +6 -13
  152. package/dist/extractors/ruby.js.map +1 -1
  153. package/dist/extractors/rust.d.ts.map +1 -1
  154. package/dist/extractors/rust.js +58 -83
  155. package/dist/extractors/rust.js.map +1 -1
  156. package/dist/extractors/scala.d.ts +6 -0
  157. package/dist/extractors/scala.d.ts.map +1 -0
  158. package/dist/extractors/scala.js +269 -0
  159. package/dist/extractors/scala.js.map +1 -0
  160. package/dist/extractors/swift.d.ts +6 -0
  161. package/dist/extractors/swift.d.ts.map +1 -0
  162. package/dist/extractors/swift.js +275 -0
  163. package/dist/extractors/swift.js.map +1 -0
  164. package/dist/extractors/zig.d.ts +9 -0
  165. package/dist/extractors/zig.d.ts.map +1 -0
  166. package/dist/extractors/zig.js +276 -0
  167. package/dist/extractors/zig.js.map +1 -0
  168. package/dist/features/ast.d.ts +2 -0
  169. package/dist/features/ast.d.ts.map +1 -1
  170. package/dist/features/ast.js +9 -24
  171. package/dist/features/ast.js.map +1 -1
  172. package/dist/features/audit.d.ts.map +1 -1
  173. package/dist/features/audit.js +17 -21
  174. package/dist/features/audit.js.map +1 -1
  175. package/dist/features/branch-compare.d.ts.map +1 -1
  176. package/dist/features/branch-compare.js +47 -3
  177. package/dist/features/branch-compare.js.map +1 -1
  178. package/dist/features/cfg.d.ts +7 -1
  179. package/dist/features/cfg.d.ts.map +1 -1
  180. package/dist/features/cfg.js +72 -61
  181. package/dist/features/cfg.js.map +1 -1
  182. package/dist/features/check.d.ts.map +1 -1
  183. package/dist/features/check.js +79 -62
  184. package/dist/features/check.js.map +1 -1
  185. package/dist/features/complexity-query.d.ts.map +1 -1
  186. package/dist/features/complexity-query.js +142 -137
  187. package/dist/features/complexity-query.js.map +1 -1
  188. package/dist/features/complexity.d.ts +7 -1
  189. package/dist/features/complexity.d.ts.map +1 -1
  190. package/dist/features/complexity.js +62 -1
  191. package/dist/features/complexity.js.map +1 -1
  192. package/dist/features/dataflow.d.ts +7 -1
  193. package/dist/features/dataflow.d.ts.map +1 -1
  194. package/dist/features/dataflow.js +356 -188
  195. package/dist/features/dataflow.js.map +1 -1
  196. package/dist/features/graph-enrichment.d.ts.map +1 -1
  197. package/dist/features/graph-enrichment.js +117 -104
  198. package/dist/features/graph-enrichment.js.map +1 -1
  199. package/dist/features/sequence.d.ts.map +1 -1
  200. package/dist/features/sequence.js +25 -4
  201. package/dist/features/sequence.js.map +1 -1
  202. package/dist/features/structure-query.d.ts.map +1 -1
  203. package/dist/features/structure-query.js +29 -4
  204. package/dist/features/structure-query.js.map +1 -1
  205. package/dist/features/structure.d.ts.map +1 -1
  206. package/dist/features/structure.js +35 -15
  207. package/dist/features/structure.js.map +1 -1
  208. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  209. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  210. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  211. package/dist/graph/algorithms/leiden/index.js +43 -28
  212. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  213. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  214. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  215. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  216. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  217. package/dist/graph/algorithms/leiden/partition.js +89 -106
  218. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  219. package/dist/graph/model.d.ts +2 -0
  220. package/dist/graph/model.d.ts.map +1 -1
  221. package/dist/graph/model.js +20 -8
  222. package/dist/graph/model.js.map +1 -1
  223. package/dist/infrastructure/config.d.ts +0 -8
  224. package/dist/infrastructure/config.d.ts.map +1 -1
  225. package/dist/infrastructure/config.js +73 -62
  226. package/dist/infrastructure/config.js.map +1 -1
  227. package/dist/infrastructure/registry.d.ts +0 -8
  228. package/dist/infrastructure/registry.d.ts.map +1 -1
  229. package/dist/infrastructure/registry.js +12 -14
  230. package/dist/infrastructure/registry.js.map +1 -1
  231. package/dist/mcp/server.d.ts.map +1 -1
  232. package/dist/mcp/server.js +45 -36
  233. package/dist/mcp/server.js.map +1 -1
  234. package/dist/presentation/audit.d.ts.map +1 -1
  235. package/dist/presentation/audit.js +61 -57
  236. package/dist/presentation/audit.js.map +1 -1
  237. package/dist/presentation/branch-compare.d.ts.map +1 -1
  238. package/dist/presentation/branch-compare.js +56 -38
  239. package/dist/presentation/branch-compare.js.map +1 -1
  240. package/dist/presentation/check.d.ts.map +1 -1
  241. package/dist/presentation/check.js +30 -32
  242. package/dist/presentation/check.js.map +1 -1
  243. package/dist/presentation/colors.d.ts.map +1 -1
  244. package/dist/presentation/colors.js +2 -0
  245. package/dist/presentation/colors.js.map +1 -1
  246. package/dist/presentation/complexity.d.ts.map +1 -1
  247. package/dist/presentation/complexity.js +25 -19
  248. package/dist/presentation/complexity.js.map +1 -1
  249. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  250. package/dist/presentation/queries-cli/exports.js +15 -15
  251. package/dist/presentation/queries-cli/exports.js.map +1 -1
  252. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  253. package/dist/presentation/queries-cli/impact.js +29 -19
  254. package/dist/presentation/queries-cli/impact.js.map +1 -1
  255. package/dist/types.d.ts +182 -7
  256. package/dist/types.d.ts.map +1 -1
  257. package/grammars/tree-sitter-bash.wasm +0 -0
  258. package/grammars/tree-sitter-c.wasm +0 -0
  259. package/grammars/tree-sitter-cpp.wasm +0 -0
  260. package/grammars/tree-sitter-dart.wasm +0 -0
  261. package/grammars/tree-sitter-elixir.wasm +0 -0
  262. package/grammars/tree-sitter-haskell.wasm +0 -0
  263. package/grammars/tree-sitter-kotlin.wasm +0 -0
  264. package/grammars/tree-sitter-lua.wasm +0 -0
  265. package/grammars/tree-sitter-ocaml.wasm +0 -0
  266. package/grammars/tree-sitter-scala.wasm +0 -0
  267. package/grammars/tree-sitter-swift.wasm +0 -0
  268. package/grammars/tree-sitter-zig.wasm +0 -0
  269. package/package.json +19 -7
  270. package/src/ast-analysis/engine.ts +147 -138
  271. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  272. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  273. package/src/db/connection.ts +90 -59
  274. package/src/db/index.ts +1 -0
  275. package/src/db/migrations.ts +36 -32
  276. package/src/domain/analysis/context.ts +73 -75
  277. package/src/domain/analysis/dependencies.ts +78 -68
  278. package/src/domain/analysis/exports.ts +45 -34
  279. package/src/domain/analysis/fn-impact.ts +67 -64
  280. package/src/domain/analysis/module-map.ts +103 -8
  281. package/src/domain/analysis/query-helpers.ts +35 -0
  282. package/src/domain/graph/builder/helpers.ts +12 -6
  283. package/src/domain/graph/builder/incremental.ts +3 -2
  284. package/src/domain/graph/builder/pipeline.ts +71 -3
  285. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  286. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  287. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  288. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  289. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  290. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  291. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  292. package/src/domain/graph/resolve.ts +34 -46
  293. package/src/domain/graph/watcher.ts +12 -14
  294. package/src/domain/parser.ts +222 -97
  295. package/src/domain/search/search/cli-formatter.ts +121 -94
  296. package/src/extractors/bash.ts +97 -0
  297. package/src/extractors/c.ts +212 -0
  298. package/src/extractors/cpp.ts +298 -0
  299. package/src/extractors/csharp.ts +53 -56
  300. package/src/extractors/dart.ts +304 -0
  301. package/src/extractors/elixir.ts +251 -0
  302. package/src/extractors/go.ts +152 -134
  303. package/src/extractors/haskell.ts +235 -0
  304. package/src/extractors/hcl.ts +6 -6
  305. package/src/extractors/helpers.ts +93 -1
  306. package/src/extractors/index.ts +12 -0
  307. package/src/extractors/java.ts +43 -48
  308. package/src/extractors/javascript.ts +328 -281
  309. package/src/extractors/kotlin.ts +293 -0
  310. package/src/extractors/lua.ts +169 -0
  311. package/src/extractors/ocaml.ts +259 -0
  312. package/src/extractors/php.ts +46 -40
  313. package/src/extractors/python.ts +81 -104
  314. package/src/extractors/ruby.ts +6 -13
  315. package/src/extractors/rust.ts +65 -85
  316. package/src/extractors/scala.ts +285 -0
  317. package/src/extractors/swift.ts +293 -0
  318. package/src/extractors/zig.ts +294 -0
  319. package/src/features/ast.ts +10 -25
  320. package/src/features/audit.ts +24 -20
  321. package/src/features/branch-compare.ts +51 -4
  322. package/src/features/cfg.ts +113 -65
  323. package/src/features/check.ts +90 -74
  324. package/src/features/complexity-query.ts +181 -163
  325. package/src/features/complexity.ts +64 -1
  326. package/src/features/dataflow.ts +462 -217
  327. package/src/features/graph-enrichment.ts +161 -117
  328. package/src/features/sequence.ts +27 -4
  329. package/src/features/structure-query.ts +43 -4
  330. package/src/features/structure.ts +50 -22
  331. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  332. package/src/graph/algorithms/leiden/index.ts +67 -28
  333. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  334. package/src/graph/algorithms/leiden/partition.ts +131 -98
  335. package/src/graph/model.ts +19 -7
  336. package/src/infrastructure/config.ts +60 -58
  337. package/src/infrastructure/registry.ts +17 -14
  338. package/src/mcp/server.ts +46 -37
  339. package/src/presentation/audit.ts +72 -67
  340. package/src/presentation/branch-compare.ts +54 -50
  341. package/src/presentation/check.ts +34 -34
  342. package/src/presentation/colors.ts +2 -0
  343. package/src/presentation/complexity.ts +39 -33
  344. package/src/presentation/queries-cli/exports.ts +17 -17
  345. package/src/presentation/queries-cli/impact.ts +30 -22
  346. package/src/types.ts +195 -7
@@ -29,6 +29,23 @@ function getPackageVersion(): string {
29
29
  /** Warn once per process when DB version mismatches the running codegraph version. */
30
30
  let _versionWarned = false;
31
31
 
32
+ /** Check and warn (once) if the running codegraph version differs from the DB build version. */
33
+ function warnOnVersionMismatch(getBuildVersion: () => string | undefined | null): void {
34
+ if (_versionWarned) return;
35
+ _versionWarned = true;
36
+ try {
37
+ const buildVersion = getBuildVersion();
38
+ const currentVersion = getPackageVersion();
39
+ if (buildVersion && currentVersion && buildVersion !== currentVersion) {
40
+ warn(
41
+ `DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
42
+ );
43
+ }
44
+ } catch {
45
+ // build_meta table may not exist in older DBs — silently ignore
46
+ }
47
+ }
48
+
32
49
  /** DB instance with optional advisory lock path. */
33
50
  export type LockedDatabase = BetterSqlite3Database & { __lockPath?: string };
34
51
 
@@ -81,11 +98,6 @@ export function _resetRepoRootCache(): void {
81
98
  _cachedRepoRootCwd = undefined;
82
99
  }
83
100
 
84
- /** Reset the version warning flag (for testing). */
85
- export function _resetVersionWarning(): void {
86
- _versionWarned = false;
87
- }
88
-
89
101
  function isProcessAlive(pid: number): boolean {
90
102
  try {
91
103
  process.kill(pid, 0);
@@ -299,28 +311,41 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database {
299
311
  const Database = getDatabase();
300
312
  const db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database;
301
313
 
302
- // Warn once per process if the DB was built with a different codegraph version
303
- if (!_versionWarned) {
304
- try {
305
- const row = db
306
- .prepare<{ value: string }>('SELECT value FROM build_meta WHERE key = ?')
307
- .get('codegraph_version');
308
- const buildVersion = row?.value;
309
- const currentVersion = getPackageVersion();
310
- if (buildVersion && currentVersion && buildVersion !== currentVersion) {
311
- warn(
312
- `DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
313
- );
314
- }
315
- } catch {
316
- // build_meta table may not exist in older DBs — silently ignore
317
- }
318
- _versionWarned = true;
319
- }
314
+ warnOnVersionMismatch(() => {
315
+ const row = db
316
+ .prepare<{ value: string }>('SELECT value FROM build_meta WHERE key = ?')
317
+ .get('codegraph_version');
318
+ return row?.value;
319
+ });
320
320
 
321
321
  return db;
322
322
  }
323
323
 
324
+ /** Open a NativeRepository via rusqlite, throwing DbError if the DB file is missing. */
325
+ function openRepoNative(customDbPath?: string): { repo: Repository; close(): void } {
326
+ const dbPath = findDbPath(customDbPath);
327
+ if (!fs.existsSync(dbPath)) {
328
+ throw new DbError(
329
+ `No codegraph database found at ${dbPath}.\nRun "codegraph build" first to analyze your codebase.`,
330
+ { file: dbPath },
331
+ );
332
+ }
333
+ const native = getNative();
334
+ const ndb = native.NativeDatabase.openReadonly(dbPath);
335
+ try {
336
+ warnOnVersionMismatch(() => ndb.getBuildMeta('codegraph_version'));
337
+ return {
338
+ repo: new NativeRepository(ndb),
339
+ close() {
340
+ ndb.close();
341
+ },
342
+ };
343
+ } catch (innerErr) {
344
+ ndb.close();
345
+ throw innerErr;
346
+ }
347
+ }
348
+
324
349
  /**
325
350
  * Open a Repository from either an injected instance or a DB path.
326
351
  *
@@ -345,42 +370,7 @@ export function openRepo(
345
370
  // Try native rusqlite path first (Phase 6.14)
346
371
  if (isNativeAvailable()) {
347
372
  try {
348
- const dbPath = findDbPath(customDbPath);
349
- if (!fs.existsSync(dbPath)) {
350
- throw new DbError(
351
- `No codegraph database found at ${dbPath}.\nRun "codegraph build" first to analyze your codebase.`,
352
- { file: dbPath },
353
- );
354
- }
355
- const native = getNative();
356
- const ndb = native.NativeDatabase.openReadonly(dbPath);
357
- try {
358
- // Version check (same logic as openReadonlyOrFail)
359
- if (!_versionWarned) {
360
- try {
361
- const buildVersion = ndb.getBuildMeta('codegraph_version');
362
- const currentVersion = getPackageVersion();
363
- if (buildVersion && currentVersion && buildVersion !== currentVersion) {
364
- warn(
365
- `DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
366
- );
367
- }
368
- } catch {
369
- // build_meta table may not exist in older DBs
370
- }
371
- _versionWarned = true;
372
- }
373
-
374
- return {
375
- repo: new NativeRepository(ndb),
376
- close() {
377
- ndb.close();
378
- },
379
- };
380
- } catch (innerErr) {
381
- ndb.close();
382
- throw innerErr;
383
- }
373
+ return openRepoNative(customDbPath);
384
374
  } catch (e) {
385
375
  // Re-throw user-visible errors (e.g. DB not found) — only silently
386
376
  // fall back for native-engine failures (e.g. incompatible native binary).
@@ -399,3 +389,44 @@ export function openRepo(
399
389
  },
400
390
  };
401
391
  }
392
+
393
+ /**
394
+ * Open a readonly DB with an optional NativeDatabase alongside it.
395
+ *
396
+ * Returns the better-sqlite3 handle (for backwards compat) plus an optional
397
+ * NativeDatabase for modules that can use batched Rust query methods.
398
+ * Callers should use nativeDb when available and fall back to db.prepare().
399
+ */
400
+ export function openReadonlyWithNative(customPath?: string): {
401
+ db: BetterSqlite3Database;
402
+ nativeDb: NativeDatabase | undefined;
403
+ close(): void;
404
+ } {
405
+ const db = openReadonlyOrFail(customPath);
406
+
407
+ let nativeDb: NativeDatabase | undefined;
408
+ if (isNativeAvailable()) {
409
+ try {
410
+ const dbPath = findDbPath(customPath);
411
+ const native = getNative();
412
+ nativeDb = native.NativeDatabase.openReadonly(dbPath);
413
+ } catch (e) {
414
+ debug(`openReadonlyWithNative: native path failed: ${(e as Error).message}`);
415
+ }
416
+ }
417
+
418
+ return {
419
+ db,
420
+ nativeDb,
421
+ close() {
422
+ db.close();
423
+ if (nativeDb) {
424
+ try {
425
+ nativeDb.close();
426
+ } catch {
427
+ // already closed or not closeable
428
+ }
429
+ }
430
+ },
431
+ };
432
+ }
package/src/db/index.ts CHANGED
@@ -11,6 +11,7 @@ export {
11
11
  flushDeferredClose,
12
12
  openDb,
13
13
  openReadonlyOrFail,
14
+ openReadonlyWithNative,
14
15
  openRepo,
15
16
  } from './connection.js';
16
17
  export { getBuildMeta, initSchema, MIGRATIONS, setBuildMeta } from './migrations.js';
@@ -304,7 +304,8 @@ export function setBuildMeta(
304
304
  tx();
305
305
  }
306
306
 
307
- export function initSchema(db: BetterSqlite3Database): void {
307
+ /** Run numbered migrations that haven't been applied yet. */
308
+ function applyMigrations(db: BetterSqlite3Database): void {
308
309
  db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`);
309
310
 
310
311
  const row = db.prepare<{ version: number }>('SELECT version FROM schema_version').get();
@@ -322,40 +323,43 @@ export function initSchema(db: BetterSqlite3Database): void {
322
323
  currentVersion = migration.version;
323
324
  }
324
325
  }
326
+ }
325
327
 
326
- // Legacy column compat — add columns that may be missing from pre-migration DBs
328
+ /** Ensure columns and indexes exist for pre-migration DBs (legacy compat). */
329
+ function ensureLegacyColumns(db: BetterSqlite3Database): void {
327
330
  if (hasTable(db, 'nodes')) {
328
- if (!hasColumn(db, 'nodes', 'end_line')) {
329
- db.exec('ALTER TABLE nodes ADD COLUMN end_line INTEGER');
330
- }
331
- if (!hasColumn(db, 'nodes', 'role')) {
332
- db.exec('ALTER TABLE nodes ADD COLUMN role TEXT');
333
- }
334
- db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_role ON nodes(role)');
335
- if (!hasColumn(db, 'nodes', 'parent_id')) {
336
- db.exec('ALTER TABLE nodes ADD COLUMN parent_id INTEGER REFERENCES nodes(id)');
337
- }
338
- db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent_id)');
339
- db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_kind_parent ON nodes(kind, parent_id)');
340
- if (!hasColumn(db, 'nodes', 'qualified_name')) {
341
- db.exec('ALTER TABLE nodes ADD COLUMN qualified_name TEXT');
342
- }
343
- if (!hasColumn(db, 'nodes', 'scope')) {
344
- db.exec('ALTER TABLE nodes ADD COLUMN scope TEXT');
345
- }
346
- if (!hasColumn(db, 'nodes', 'visibility')) {
347
- db.exec('ALTER TABLE nodes ADD COLUMN visibility TEXT');
348
- }
349
- db.exec('UPDATE nodes SET qualified_name = name WHERE qualified_name IS NULL');
350
- db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name)');
351
- db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_scope ON nodes(scope)');
331
+ ensureNodeColumns(db);
352
332
  }
353
333
  if (hasTable(db, 'edges')) {
354
- if (!hasColumn(db, 'edges', 'confidence')) {
355
- db.exec('ALTER TABLE edges ADD COLUMN confidence REAL DEFAULT 1.0');
356
- }
357
- if (!hasColumn(db, 'edges', 'dynamic')) {
358
- db.exec('ALTER TABLE edges ADD COLUMN dynamic INTEGER DEFAULT 0');
359
- }
334
+ ensureEdgeColumns(db);
360
335
  }
361
336
  }
337
+
338
+ function ensureNodeColumns(db: BetterSqlite3Database): void {
339
+ const missing = (col: string) => !hasColumn(db, 'nodes', col);
340
+ if (missing('end_line')) db.exec('ALTER TABLE nodes ADD COLUMN end_line INTEGER');
341
+ if (missing('role')) db.exec('ALTER TABLE nodes ADD COLUMN role TEXT');
342
+ db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_role ON nodes(role)');
343
+ if (missing('parent_id'))
344
+ db.exec('ALTER TABLE nodes ADD COLUMN parent_id INTEGER REFERENCES nodes(id)');
345
+ db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent_id)');
346
+ db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_kind_parent ON nodes(kind, parent_id)');
347
+ if (missing('qualified_name')) db.exec('ALTER TABLE nodes ADD COLUMN qualified_name TEXT');
348
+ if (missing('scope')) db.exec('ALTER TABLE nodes ADD COLUMN scope TEXT');
349
+ if (missing('visibility')) db.exec('ALTER TABLE nodes ADD COLUMN visibility TEXT');
350
+ db.exec('UPDATE nodes SET qualified_name = name WHERE qualified_name IS NULL');
351
+ db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name)');
352
+ db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_scope ON nodes(scope)');
353
+ }
354
+
355
+ function ensureEdgeColumns(db: BetterSqlite3Database): void {
356
+ if (!hasColumn(db, 'edges', 'confidence'))
357
+ db.exec('ALTER TABLE edges ADD COLUMN confidence REAL DEFAULT 1.0');
358
+ if (!hasColumn(db, 'edges', 'dynamic'))
359
+ db.exec('ALTER TABLE edges ADD COLUMN dynamic INTEGER DEFAULT 0');
360
+ }
361
+
362
+ export function initSchema(db: BetterSqlite3Database): void {
363
+ applyMigrations(db);
364
+ ensureLegacyColumns(db);
365
+ }
@@ -15,10 +15,8 @@ import {
15
15
  getComplexityForNode,
16
16
  getLineCountForNode,
17
17
  getMaxEndLineForFile,
18
- openReadonlyOrFail,
19
18
  } from '../../db/index.js';
20
19
  import { cachedStmt } from '../../db/repository/cached-stmt.js';
21
- import { loadConfig } from '../../infrastructure/config.js';
22
20
  import { debug } from '../../infrastructure/logger.js';
23
21
  import { isTestFile } from '../../infrastructure/test-filter.js';
24
22
  import {
@@ -40,6 +38,7 @@ import type {
40
38
  RelatedNodeRow,
41
39
  StmtCache,
42
40
  } from '../../types.js';
41
+ import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
43
42
  import { findMatchingNodes } from './symbol-lookup.js';
44
43
 
45
44
  interface DisplayOpts {
@@ -52,6 +51,60 @@ interface DisplayOpts {
52
51
  [key: string]: unknown;
53
52
  }
54
53
 
54
+ /** Format a callee row into the output shape with summary and source. */
55
+ function formatCalleeRow(
56
+ c: RelatedNodeRow,
57
+ repoRoot: string,
58
+ getFileLines: (file: string) => string[] | null,
59
+ displayOpts: DisplayOpts,
60
+ includeSource: boolean,
61
+ ) {
62
+ const cLines = getFileLines(c.file);
63
+ return {
64
+ name: c.name,
65
+ kind: c.kind,
66
+ file: c.file,
67
+ line: c.line,
68
+ endLine: c.end_line || null,
69
+ summary: cLines ? extractSummary(cLines, c.line, displayOpts) : null,
70
+ source: includeSource
71
+ ? readSourceRange(repoRoot, c.file, c.line, c.end_line ?? undefined, displayOpts)
72
+ : null,
73
+ };
74
+ }
75
+
76
+ /** BFS to collect deeper callees beyond the first level. */
77
+ function collectDeeperCallees(
78
+ db: BetterSqlite3Database,
79
+ startIds: number[],
80
+ rootId: number,
81
+ repoRoot: string,
82
+ getFileLines: (file: string) => string[] | null,
83
+ opts: { noTests: boolean; maxDepth: number; displayOpts: DisplayOpts },
84
+ ) {
85
+ const { noTests, maxDepth, displayOpts } = opts;
86
+ const visited = new Set(startIds);
87
+ visited.add(rootId);
88
+ let frontier = [...startIds];
89
+ const result: ReturnType<typeof formatCalleeRow>[] = [];
90
+
91
+ for (let d = 2; d <= maxDepth; d++) {
92
+ const nextFrontier: number[] = [];
93
+ for (const fid of frontier) {
94
+ const deeper = findCallees(db, fid) as RelatedNodeRow[];
95
+ for (const c of deeper) {
96
+ if (visited.has(c.id) || (noTests && isTestFile(c.file))) continue;
97
+ visited.add(c.id);
98
+ nextFrontier.push(c.id);
99
+ result.push(formatCalleeRow(c, repoRoot, getFileLines, displayOpts, true));
100
+ }
101
+ }
102
+ frontier = nextFrontier;
103
+ if (frontier.length === 0) break;
104
+ }
105
+ return result;
106
+ }
107
+
55
108
  function buildCallees(
56
109
  db: BetterSqlite3Database,
57
110
  node: NodeRow,
@@ -63,65 +116,20 @@ function buildCallees(
63
116
  const calleeRows = findCallees(db, node.id) as RelatedNodeRow[];
64
117
  const filteredCallees = noTests ? calleeRows.filter((c) => !isTestFile(c.file)) : calleeRows;
65
118
 
66
- const callees = filteredCallees.map((c) => {
67
- const cLines = getFileLines(c.file);
68
- const summary = cLines ? extractSummary(cLines, c.line, displayOpts) : null;
69
- let calleeSource: string | null = null;
70
- if (depth >= 1) {
71
- calleeSource = readSourceRange(
72
- repoRoot,
73
- c.file,
74
- c.line,
75
- c.end_line ?? undefined,
76
- displayOpts,
77
- );
78
- }
79
- return {
80
- name: c.name,
81
- kind: c.kind,
82
- file: c.file,
83
- line: c.line,
84
- endLine: c.end_line || null,
85
- summary,
86
- source: calleeSource,
87
- };
88
- });
119
+ const callees = filteredCallees.map((c) =>
120
+ formatCalleeRow(c, repoRoot, getFileLines, displayOpts, depth >= 1),
121
+ );
89
122
 
90
123
  if (depth > 1) {
91
- const visited = new Set(filteredCallees.map((c) => c.id));
92
- visited.add(node.id);
93
- let frontier = filteredCallees.map((c) => c.id);
94
- const maxDepth = Math.min(depth, 5);
95
- for (let d = 2; d <= maxDepth; d++) {
96
- const nextFrontier: number[] = [];
97
- for (const fid of frontier) {
98
- const deeper = findCallees(db, fid) as RelatedNodeRow[];
99
- for (const c of deeper) {
100
- if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
101
- visited.add(c.id);
102
- nextFrontier.push(c.id);
103
- const cLines = getFileLines(c.file);
104
- callees.push({
105
- name: c.name,
106
- kind: c.kind,
107
- file: c.file,
108
- line: c.line,
109
- endLine: c.end_line || null,
110
- summary: cLines ? extractSummary(cLines, c.line, displayOpts) : null,
111
- source: readSourceRange(
112
- repoRoot,
113
- c.file,
114
- c.line,
115
- c.end_line ?? undefined,
116
- displayOpts,
117
- ),
118
- });
119
- }
120
- }
121
- }
122
- frontier = nextFrontier;
123
- if (frontier.length === 0) break;
124
- }
124
+ const deeper = collectDeeperCallees(
125
+ db,
126
+ filteredCallees.map((c) => c.id),
127
+ node.id,
128
+ repoRoot,
129
+ getFileLines,
130
+ { noTests, maxDepth: Math.min(depth, 5), displayOpts },
131
+ );
132
+ callees.push(...deeper);
125
133
  }
126
134
 
127
135
  return callees;
@@ -433,15 +441,12 @@ export function contextData(
433
441
  config?: any;
434
442
  } = {},
435
443
  ) {
436
- const db = openReadonlyOrFail(customDbPath);
437
- try {
444
+ return withReadonlyDb(customDbPath, (db) => {
438
445
  const depth = opts.depth || 0;
439
446
  const noSource = opts.noSource || false;
440
- const noTests = opts.noTests || false;
441
447
  const includeTests = opts.includeTests || false;
442
448
 
443
- const config = opts.config || loadConfig();
444
- const displayOpts: DisplayOpts = config.display || {};
449
+ const { noTests, displayOpts } = resolveAnalysisOpts(opts);
445
450
 
446
451
  const dbPath = findDbPath(customDbPath);
447
452
  const repoRoot = path.resolve(path.dirname(dbPath), '..');
@@ -494,9 +499,7 @@ export function contextData(
494
499
 
495
500
  const base = { name, results };
496
501
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
497
- } finally {
498
- db.close();
499
- }
502
+ });
500
503
  }
501
504
 
502
505
  export function explainData(
@@ -510,14 +513,11 @@ export function explainData(
510
513
  config?: any;
511
514
  } = {},
512
515
  ) {
513
- const db = openReadonlyOrFail(customDbPath);
514
- try {
515
- const noTests = opts.noTests || false;
516
+ return withReadonlyDb(customDbPath, (db) => {
516
517
  const depth = opts.depth || 0;
517
518
  const kind = isFileLikeTarget(target) ? 'file' : 'function';
518
519
 
519
- const config = opts.config || loadConfig();
520
- const displayOpts: DisplayOpts = config.display || {};
520
+ const { noTests, displayOpts } = resolveAnalysisOpts(opts);
521
521
 
522
522
  const dbPath = findDbPath(customDbPath);
523
523
  const repoRoot = path.resolve(path.dirname(dbPath), '..');
@@ -536,7 +536,5 @@ export function explainData(
536
536
 
537
537
  const base = { target, kind, results };
538
538
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
539
- } finally {
540
- db.close();
541
- }
539
+ });
542
540
  }