@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
@@ -6,9 +6,9 @@
6
6
  */
7
7
  import path from 'node:path';
8
8
  import { performance } from 'node:perf_hooks';
9
- import { getNodeId } from '#db/index.js';
10
- import { debug } from '#infrastructure/logger.js';
11
- import { loadNative } from '#infrastructure/native.js';
9
+ import { getNodeId } from '../../../../db/index.js';
10
+ import { debug } from '../../../../infrastructure/logger.js';
11
+ import { loadNative } from '../../../../infrastructure/native.js';
12
12
  import type {
13
13
  BetterSqlite3Database,
14
14
  Call,
@@ -18,10 +18,11 @@ import type {
18
18
  NativeAddon,
19
19
  NodeRow,
20
20
  TypeMapEntry,
21
- } from '#types';
21
+ } from '../../../../types.js';
22
22
  import { computeConfidence } from '../../resolve.js';
23
23
  import type { PipelineContext } from '../context.js';
24
24
  import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
25
+
25
26
  import { getResolved, isBarrelFile, resolveBarrelExport } from './resolve-imports.js';
26
27
 
27
28
  // ── Local types ──────────────────────────────────────────────────────────
@@ -61,12 +62,6 @@ interface NativeEdge {
61
62
  dynamic: number;
62
63
  }
63
64
 
64
- /** TypeMap entry used in receiver supplement (normalized from native format). */
65
- interface NormalizedTypeEntry {
66
- type: string;
67
- confidence: number;
68
- }
69
-
70
65
  // ── Node lookup setup ───────────────────────────────────────────────────
71
66
 
72
67
  function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
@@ -210,14 +205,6 @@ function buildCallEdgesNative(
210
205
  for (const e of nativeEdges) {
211
206
  allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
212
207
  }
213
-
214
- // Older native binaries (< 3.2.0) don't emit receiver or type-resolved method-call
215
- // edges. Supplement them on the JS side if the native binary missed them.
216
- // TODO: Remove once all published native binaries handle receivers (>= 3.2.0)
217
- const hasReceiver = nativeEdges.some((e) => e.kind === 'receiver');
218
- if (!hasReceiver) {
219
- supplementReceiverEdges(ctx, nativeFiles, getNodeIdStmt, allEdgeRows);
220
- }
221
208
  }
222
209
 
223
210
  function buildImportedNamesForNative(
@@ -242,58 +229,6 @@ function buildImportedNamesForNative(
242
229
  return importedNames;
243
230
  }
244
231
 
245
- // ── Receiver edge supplement for older native binaries ──────────────────
246
-
247
- function supplementReceiverEdges(
248
- ctx: PipelineContext,
249
- nativeFiles: NativeFileEntry[],
250
- getNodeIdStmt: NodeIdStmt,
251
- allEdgeRows: EdgeRowTuple[],
252
- ): void {
253
- const seenCallEdges = new Set<string>();
254
- // Collect existing edges to avoid duplicates
255
- for (const row of allEdgeRows) {
256
- seenCallEdges.add(`${row[0]}|${row[1]}|${row[2]}`);
257
- }
258
-
259
- for (const nf of nativeFiles) {
260
- const relPath = nf.file;
261
- const typeMap = new Map<string, NormalizedTypeEntry>(
262
- nf.typeMap.map((t) => [t.name, { type: t.typeName, confidence: t.confidence ?? 0.9 }]),
263
- );
264
- const fileNodeRow = { id: nf.fileNodeId };
265
-
266
- for (const call of nf.calls) {
267
- if (!call.receiver || BUILTIN_RECEIVERS.has(call.receiver)) continue;
268
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super')
269
- continue;
270
-
271
- const caller = findCaller(call, nf.definitions, relPath, getNodeIdStmt, fileNodeRow);
272
-
273
- // Receiver edge: caller → receiver type node
274
- buildReceiverEdge(ctx, call, caller, relPath, seenCallEdges, allEdgeRows, typeMap);
275
-
276
- // Type-resolved method call: caller → Type.method
277
- const typeEntry = typeMap.get(call.receiver);
278
- const typeName = typeEntry ? typeEntry.type : null;
279
- if (typeName) {
280
- const qualifiedName = `${typeName}.${call.name}`;
281
- const targets = (ctx.nodesByName.get(qualifiedName) || []).filter(
282
- (n) => n.kind === 'method',
283
- );
284
- for (const t of targets) {
285
- const key = `${caller.id}|${t.id}|calls`;
286
- if (t.id !== caller.id && !seenCallEdges.has(key)) {
287
- seenCallEdges.add(key);
288
- const confidence = computeConfidence(relPath, t.file, null);
289
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0]);
290
- }
291
- }
292
- }
293
- }
294
- }
295
- }
296
-
297
232
  // ── Call edges (JS fallback) ────────────────────────────────────────────
298
233
 
299
234
  function buildCallEdgesJS(
@@ -495,7 +430,7 @@ function buildReceiverEdge(
495
430
  relPath: string,
496
431
  seenCallEdges: Set<string>,
497
432
  allEdgeRows: EdgeRowTuple[],
498
- typeMap: Map<string, TypeMapEntry | NormalizedTypeEntry | string>,
433
+ typeMap: Map<string, TypeMapEntry | string>,
499
434
  ): void {
500
435
  const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
501
436
  const typeEntry = typeMap?.get(call.receiver!);
@@ -608,7 +543,7 @@ function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolea
608
543
 
609
544
  /**
610
545
  * For scoped node loading, patch nodesByName.get with a lazy SQL fallback
611
- * so global name-only lookups (resolveByMethodOrGlobal, supplementReceiverEdges)
546
+ * so global name-only lookups (resolveByMethodOrGlobal)
612
547
  * can still find nodes outside the scoped set.
613
548
  */
614
549
  function addLazyFallback(ctx: PipelineContext, scopedLoad: boolean): void {
@@ -673,7 +608,7 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
673
608
 
674
609
  // When using native edge insert, skip JS insert here — do it after tx commits.
675
610
  // Otherwise insert edges within this transaction for atomicity.
676
- const useNativeEdgeInsert = !!ctx.nativeDb?.bulkInsertEdges;
611
+ const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
677
612
  if (!useNativeEdgeInsert) {
678
613
  batchInsertEdges(db, allEdgeRows);
679
614
  }
@@ -683,7 +618,7 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
683
618
  // Phase 2: Native rusqlite bulk insert (outside better-sqlite3 transaction
684
619
  // to avoid SQLITE_BUSY contention). Uses NativeDatabase persistent connection.
685
620
  // Standalone napi functions were removed in 6.17.
686
- if (ctx.nativeDb?.bulkInsertEdges && allEdgeRows.length > 0) {
621
+ if (ctx.engineName === 'native' && ctx.nativeDb?.bulkInsertEdges && allEdgeRows.length > 0) {
687
622
  const nativeEdges = allEdgeRows.map((r) => ({
688
623
  sourceId: r[0],
689
624
  targetId: r[1],
@@ -694,7 +629,7 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
694
629
  const ok = ctx.nativeDb.bulkInsertEdges(nativeEdges);
695
630
  if (!ok) {
696
631
  debug('Native bulkInsertEdges failed — falling back to JS batchInsertEdges');
697
- batchInsertEdges(db, allEdgeRows);
632
+ batchInsertEdges(ctx.db, allEdgeRows);
698
633
  }
699
634
  }
700
635
 
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import path from 'node:path';
7
7
  import { performance } from 'node:perf_hooks';
8
- import { debug } from '#infrastructure/logger.js';
9
- import { normalizePath } from '#shared/constants.js';
10
- import type { ExtractorOutput } from '#types';
8
+ import { debug } from '../../../../infrastructure/logger.js';
9
+ import { normalizePath } from '../../../../shared/constants.js';
10
+ import type { ExtractorOutput } from '../../../../types.js';
11
11
  import type { PipelineContext } from '../context.js';
12
12
  import { readFileSafe } from '../helpers.js';
13
13
 
@@ -39,10 +39,11 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
39
39
  // loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
40
40
  // Gate: ≤5 changed files AND significantly more existing files (>20) to
41
41
  // avoid triggering on small test fixtures where directory metrics matter.
42
+ const useNativeReads = ctx.engineName === 'native' && !!ctx.nativeDb;
42
43
  const existingFileCount = !isFullBuild
43
44
  ? (
44
- (ctx.nativeDb
45
- ? ctx.nativeDb.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
45
+ (useNativeReads
46
+ ? ctx.nativeDb!.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
46
47
  : db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get()) as {
47
48
  c: number;
48
49
  }
@@ -96,7 +97,8 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
96
97
 
97
98
  // Use NativeDatabase persistent connection (Phase 6.15+).
98
99
  // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
99
- if (ctx.nativeDb?.classifyRolesFull) {
100
+ // Note: classifyRoles* both read (fan-in/fan-out) and write (UPDATE nodes SET role).
101
+ if (useNativeReads && ctx.nativeDb?.classifyRolesFull) {
100
102
  const nativeResult =
101
103
  changedFileList && changedFileList.length > 0
102
104
  ? ctx.nativeDb.classifyRolesIncremental(changedFileList)
@@ -126,7 +128,7 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
126
128
  changedFiles?: string[] | null,
127
129
  ) => Record<string, number>;
128
130
  };
129
- roleSummary = classifyNodeRoles(db, changedFileList);
131
+ roleSummary = classifyNodeRoles(ctx.db, changedFileList);
130
132
  }
131
133
 
132
134
  debug(
@@ -7,8 +7,8 @@
7
7
  */
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
- import { debug, info } from '#infrastructure/logger.js';
11
- import { normalizePath } from '#shared/constants.js';
10
+ import { debug, info } from '../../../../infrastructure/logger.js';
11
+ import { normalizePath } from '../../../../shared/constants.js';
12
12
  import { readJournal } from '../../journal.js';
13
13
  import type { PipelineContext } from '../context.js';
14
14
  import { collectFiles as collectFilesUtil } from '../helpers.js';
@@ -338,7 +338,7 @@ function purgeAndAddReverseDeps(
338
338
  if (hasPurge || hasReverseDeps) {
339
339
  const filesToPurge = hasPurge ? [...ctx.removed, ...changePaths] : [];
340
340
  // Prefer NativeDatabase: purge + reverse-dep edge deletion in one transaction (#670)
341
- if (ctx.nativeDb?.purgeFilesData) {
341
+ if (ctx.engineName === 'native' && ctx.nativeDb?.purgeFilesData) {
342
342
  ctx.nativeDb.purgeFilesData(filesToPurge, false, hasReverseDeps ? reverseDepList : undefined);
343
343
  } else {
344
344
  if (hasPurge) {
@@ -433,7 +433,12 @@ export async function detectChanges(ctx: PipelineContext): Promise<void> {
433
433
  }
434
434
  const increResult =
435
435
  incremental && !forceFullRebuild
436
- ? getChangedFiles(db, allFiles, rootDir, ctx.nativeDb)
436
+ ? getChangedFiles(
437
+ db,
438
+ allFiles,
439
+ rootDir,
440
+ ctx.engineName === 'native' ? ctx.nativeDb : undefined,
441
+ )
437
442
  : {
438
443
  changed: allFiles.map((f): ChangedFile => ({ file: f })),
439
444
  removed: [] as string[],
@@ -17,12 +17,8 @@ import { CODEGRAPH_VERSION } from '../../../../shared/version.js';
17
17
  import { writeJournalHeader } from '../../journal.js';
18
18
  import type { PipelineContext } from '../context.js';
19
19
 
20
- export async function finalize(ctx: PipelineContext): Promise<void> {
21
- const { db, allSymbols, rootDir, isFullBuild, hasEmbeddings, config, opts, schemaVersion } = ctx;
22
-
23
- const t0 = performance.now();
24
-
25
- // Release cached WASM trees
20
+ /** Release cached WASM parse trees to free memory. */
21
+ function releaseWasmTrees(allSymbols: PipelineContext['allSymbols']): void {
26
22
  for (const [, symbols] of allSymbols) {
27
23
  const tree = symbols._tree as { delete?: () => void } | undefined;
28
24
  if (tree && typeof tree.delete === 'function') {
@@ -35,133 +31,141 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
35
31
  symbols._tree = undefined;
36
32
  symbols._langId = undefined;
37
33
  }
34
+ }
38
35
 
39
- // Capture a single wall-clock timestamp for the current build — used for
40
- // both the stale-embeddings comparison and the persisted built_at metadata.
41
- const buildNow = new Date();
36
+ /**
37
+ * Detect significant drift between current and previous node/edge counts.
38
+ * Skipped for small incremental changes where count fluctuation is expected.
39
+ */
40
+ function detectIncrementalDrift(
41
+ ctx: PipelineContext,
42
+ nodeCount: number,
43
+ actualEdgeCount: number,
44
+ ): void {
45
+ const { db, allSymbols, config } = ctx;
46
+ const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
47
+ if (ctx.isFullBuild || allSymbols.size <= 3) return;
42
48
 
43
- const nodeCount = (db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
44
- const actualEdgeCount = (db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number }).c;
45
- info(`Graph built: ${nodeCount} nodes, ${actualEdgeCount} edges`);
46
- info(`Stored in ${ctx.dbPath}`);
49
+ const prevNodes = useNativeDb
50
+ ? ctx.nativeDb!.getBuildMeta('node_count')
51
+ : getBuildMeta(db, 'node_count');
52
+ const prevEdges = useNativeDb
53
+ ? ctx.nativeDb!.getBuildMeta('edge_count')
54
+ : getBuildMeta(db, 'edge_count');
55
+ if (!prevNodes || !prevEdges) return;
47
56
 
48
- // Incremental drift detection — skip for small incremental changes where
49
- // count fluctuation is expected (reverse-dep edge churn).
50
- if (!isFullBuild && allSymbols.size > 3) {
51
- const prevNodes = ctx.nativeDb
52
- ? ctx.nativeDb.getBuildMeta('node_count')
53
- : getBuildMeta(db, 'node_count');
54
- const prevEdges = ctx.nativeDb
55
- ? ctx.nativeDb.getBuildMeta('edge_count')
56
- : getBuildMeta(db, 'edge_count');
57
- if (prevNodes && prevEdges) {
58
- const prevN = Number(prevNodes);
59
- const prevE = Number(prevEdges);
60
- if (prevN > 0) {
61
- const nodeDrift = Math.abs(nodeCount - prevN) / prevN;
62
- const edgeDrift = prevE > 0 ? Math.abs(actualEdgeCount - prevE) / prevE : 0;
63
- const driftThreshold =
64
- (config as { build?: { driftThreshold?: number } }).build?.driftThreshold ?? 0.2;
65
- if (nodeDrift > driftThreshold || edgeDrift > driftThreshold) {
66
- warn(
67
- `Incremental build diverged significantly from previous counts (nodes: ${prevN}\u2192${nodeCount} [${(nodeDrift * 100).toFixed(1)}%], edges: ${prevE}\u2192${actualEdgeCount} [${(edgeDrift * 100).toFixed(1)}%], threshold: ${(driftThreshold * 100).toFixed(0)}%). Consider rebuilding with --no-incremental.`,
68
- );
69
- }
70
- }
71
- }
57
+ const prevN = Number(prevNodes);
58
+ const prevE = Number(prevEdges);
59
+ if (prevN <= 0) return;
60
+
61
+ const nodeDrift = Math.abs(nodeCount - prevN) / prevN;
62
+ const edgeDrift = prevE > 0 ? Math.abs(actualEdgeCount - prevE) / prevE : 0;
63
+ const driftThreshold =
64
+ (config as { build?: { driftThreshold?: number } }).build?.driftThreshold ?? 0.2;
65
+ if (nodeDrift > driftThreshold || edgeDrift > driftThreshold) {
66
+ warn(
67
+ `Incremental build diverged significantly from previous counts (nodes: ${prevN}\u2192${nodeCount} [${(nodeDrift * 100).toFixed(1)}%], edges: ${prevE}\u2192${actualEdgeCount} [${(edgeDrift * 100).toFixed(1)}%], threshold: ${(driftThreshold * 100).toFixed(0)}%). Consider rebuilding with --no-incremental.`,
68
+ );
72
69
  }
70
+ }
73
71
 
74
- // For small incremental builds, skip persisting build metadata — the
75
- // engine/version/schema haven't changed (would have triggered a full rebuild),
76
- // built_at is only used by stale-embeddings check (skipped for incremental),
77
- // and counts are only used by drift detection (skipped for ≤3 files).
78
- // This avoids a transaction commit + WAL fsync (~15-30ms).
79
- // Threshold aligned with drift detection gate (allSymbols.size > 3) so stored
80
- // counts stay fresh whenever drift detection reads them.
81
- if (isFullBuild || allSymbols.size > 3) {
82
- try {
83
- if (ctx.nativeDb) {
84
- ctx.nativeDb.setBuildMeta(
85
- Object.entries({
86
- engine: ctx.engineName,
87
- engine_version: ctx.engineVersion || '',
88
- codegraph_version: CODEGRAPH_VERSION,
89
- schema_version: String(schemaVersion),
90
- built_at: buildNow.toISOString(),
91
- node_count: String(nodeCount),
92
- edge_count: String(actualEdgeCount),
93
- }).map(([key, value]) => ({ key, value: String(value) })),
94
- );
95
- } else {
96
- setBuildMeta(db, {
72
+ /**
73
+ * Persist build metadata (engine, version, counts, timestamp).
74
+ * Skipped for small incremental builds to avoid WAL fsync cost.
75
+ */
76
+ function persistBuildMetadata(
77
+ ctx: PipelineContext,
78
+ nodeCount: number,
79
+ actualEdgeCount: number,
80
+ buildNow: Date,
81
+ ): void {
82
+ const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
83
+ if (!ctx.isFullBuild && ctx.allSymbols.size <= 3) return;
84
+ try {
85
+ if (useNativeDb) {
86
+ ctx.nativeDb!.setBuildMeta(
87
+ Object.entries({
97
88
  engine: ctx.engineName,
98
89
  engine_version: ctx.engineVersion || '',
99
90
  codegraph_version: CODEGRAPH_VERSION,
100
- schema_version: String(schemaVersion),
91
+ schema_version: String(ctx.schemaVersion),
101
92
  built_at: buildNow.toISOString(),
102
- node_count: nodeCount,
103
- edge_count: actualEdgeCount,
104
- });
105
- }
106
- } catch (err) {
107
- warn(`Failed to write build metadata: ${(err as Error).message}`);
93
+ node_count: String(nodeCount),
94
+ edge_count: String(actualEdgeCount),
95
+ }).map(([key, value]) => ({ key, value: String(value) })),
96
+ );
97
+ } else {
98
+ setBuildMeta(ctx.db, {
99
+ engine: ctx.engineName,
100
+ engine_version: ctx.engineVersion || '',
101
+ codegraph_version: CODEGRAPH_VERSION,
102
+ schema_version: String(ctx.schemaVersion),
103
+ built_at: buildNow.toISOString(),
104
+ node_count: nodeCount,
105
+ edge_count: actualEdgeCount,
106
+ });
108
107
  }
108
+ } catch (err) {
109
+ warn(`Failed to write build metadata: ${(err as Error).message}`);
109
110
  }
111
+ }
110
112
 
111
- // Skip expensive advisory queries for incremental builds — these are
112
- // informational warnings that don't affect correctness and cost ~40-60ms.
113
- if (!isFullBuild) {
114
- debug(
115
- 'Finalize: skipping advisory queries (orphaned/stale embeddings, unused exports) for incremental build',
116
- );
117
- } else {
118
- // Orphaned embeddings warning
119
- if (hasEmbeddings) {
120
- try {
121
- const orphaned = (
122
- db
123
- .prepare(
124
- 'SELECT COUNT(*) as c FROM embeddings WHERE node_id NOT IN (SELECT id FROM nodes)',
125
- )
126
- .get() as { c: number }
127
- ).c;
128
- if (orphaned > 0) {
129
- warn(
130
- `${orphaned} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
131
- );
132
- }
133
- } catch {
134
- /* ignore - embeddings table may have been dropped */
113
+ /**
114
+ * Run advisory checks on full builds: orphaned embeddings, stale embeddings,
115
+ * and unused exports. Informational only — does not affect correctness.
116
+ */
117
+ function runAdvisoryChecks(
118
+ db: PipelineContext['db'],
119
+ hasEmbeddings: boolean,
120
+ buildNow: Date,
121
+ ): void {
122
+ // Orphaned embeddings warning
123
+ if (hasEmbeddings) {
124
+ try {
125
+ const orphaned = (
126
+ db
127
+ .prepare(
128
+ 'SELECT COUNT(*) as c FROM embeddings WHERE node_id NOT IN (SELECT id FROM nodes)',
129
+ )
130
+ .get() as { c: number }
131
+ ).c;
132
+ if (orphaned > 0) {
133
+ warn(
134
+ `${orphaned} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
135
+ );
135
136
  }
137
+ } catch {
138
+ /* ignore - embeddings table may have been dropped */
136
139
  }
140
+ }
137
141
 
138
- // Stale embeddings warning (built before current graph rebuild)
139
- if (hasEmbeddings) {
140
- try {
141
- const embedBuiltAt = (
142
- db.prepare("SELECT value FROM embedding_meta WHERE key = 'built_at'").get() as
143
- | { value: string }
144
- | undefined
145
- )?.value;
146
- if (embedBuiltAt) {
147
- const embedTime = new Date(embedBuiltAt).getTime();
148
- if (!Number.isNaN(embedTime) && embedTime < buildNow.getTime()) {
149
- warn(
150
- 'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
151
- );
152
- }
142
+ // Stale embeddings warning (built before current graph rebuild)
143
+ if (hasEmbeddings) {
144
+ try {
145
+ const embedBuiltAt = (
146
+ db.prepare("SELECT value FROM embedding_meta WHERE key = 'built_at'").get() as
147
+ | { value: string }
148
+ | undefined
149
+ )?.value;
150
+ if (embedBuiltAt) {
151
+ const embedTime = new Date(embedBuiltAt).getTime();
152
+ if (!Number.isNaN(embedTime) && embedTime < buildNow.getTime()) {
153
+ warn(
154
+ 'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
155
+ );
153
156
  }
154
- } catch {
155
- /* ignore - embedding_meta table may not exist */
156
157
  }
158
+ } catch {
159
+ /* ignore - embedding_meta table may not exist */
157
160
  }
161
+ }
158
162
 
159
- // Unused exports warning
160
- try {
161
- const unusedCount = (
162
- db
163
- .prepare(
164
- `SELECT COUNT(*) as c FROM nodes
163
+ // Unused exports warning
164
+ try {
165
+ const unusedCount = (
166
+ db
167
+ .prepare(
168
+ `SELECT COUNT(*) as c FROM nodes
165
169
  WHERE exported = 1 AND kind != 'file'
166
170
  AND id NOT IN (
167
171
  SELECT DISTINCT e.target_id FROM edges e
@@ -169,17 +173,47 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
169
173
  JOIN nodes target ON e.target_id = target.id
170
174
  WHERE e.kind = 'calls' AND caller.file != target.file
171
175
  )`,
172
- )
173
- .get() as { c: number }
174
- ).c;
175
- if (unusedCount > 0) {
176
- warn(
177
- `${unusedCount} exported symbol${unusedCount > 1 ? 's have' : ' has'} zero cross-file consumers. Run "codegraph exports <file> --unused" to inspect.`,
178
- );
179
- }
180
- } catch {
181
- /* exported column may not exist on older DBs */
176
+ )
177
+ .get() as { c: number }
178
+ ).c;
179
+ if (unusedCount > 0) {
180
+ warn(
181
+ `${unusedCount} exported symbol${unusedCount > 1 ? 's have' : ' has'} zero cross-file consumers. Run "codegraph exports <file> --unused" to inspect.`,
182
+ );
182
183
  }
184
+ } catch {
185
+ /* exported column may not exist on older DBs */
186
+ }
187
+ }
188
+
189
+ export async function finalize(ctx: PipelineContext): Promise<void> {
190
+ const { allSymbols, rootDir, isFullBuild, hasEmbeddings, opts } = ctx;
191
+
192
+ const t0 = performance.now();
193
+
194
+ releaseWasmTrees(allSymbols);
195
+
196
+ // Capture a single wall-clock timestamp for the current build — used for
197
+ // both the stale-embeddings comparison and the persisted built_at metadata.
198
+ const buildNow = new Date();
199
+
200
+ const nodeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
201
+ const actualEdgeCount = (ctx.db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number })
202
+ .c;
203
+ info(`Graph built: ${nodeCount} nodes, ${actualEdgeCount} edges`);
204
+ info(`Stored in ${ctx.dbPath}`);
205
+
206
+ detectIncrementalDrift(ctx, nodeCount, actualEdgeCount);
207
+ persistBuildMetadata(ctx, nodeCount, actualEdgeCount, buildNow);
208
+
209
+ // Skip expensive advisory queries for incremental builds — these are
210
+ // informational warnings that don't affect correctness and cost ~40-60ms.
211
+ if (!isFullBuild) {
212
+ debug(
213
+ 'Finalize: skipping advisory queries (orphaned/stale embeddings, unused exports) for incremental build',
214
+ );
215
+ } else {
216
+ runAdvisoryChecks(ctx.db, hasEmbeddings, buildNow);
183
217
  }
184
218
 
185
219
  // Intentionally measured before closeDb / writeJournalHeader / auto-registration:
@@ -192,7 +226,7 @@ export async function finalize(ctx: PipelineContext): Promise<void> {
192
226
  // For small incremental builds, defer the expensive WAL checkpoint to the
193
227
  // next event loop tick. Skip for temp directories (tests) — they rmSync
194
228
  // immediately after build.
195
- const pair = { db, nativeDb: ctx.nativeDb };
229
+ const pair = { db: ctx.db, nativeDb: ctx.nativeDb };
196
230
  const isTempDir = path.resolve(rootDir).startsWith(path.resolve(tmpdir()));
197
231
  if (!isFullBuild && allSymbols.size <= 5 && !isTempDir) {
198
232
  closeDbPairDeferred(pair);