@optave/codegraph 3.4.0 → 3.5.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 (453) hide show
  1. package/README.md +23 -22
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +3 -9
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +1 -0
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/shared.d.ts.map +1 -1
  9. package/dist/ast-analysis/shared.js +0 -1
  10. package/dist/ast-analysis/shared.js.map +1 -1
  11. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  12. package/dist/ast-analysis/visitors/ast-store-visitor.js +103 -35
  13. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  14. package/dist/ast-analysis/visitors/cfg-conditionals.d.ts +5 -0
  15. package/dist/ast-analysis/visitors/cfg-conditionals.d.ts.map +1 -0
  16. package/dist/ast-analysis/visitors/cfg-conditionals.js +166 -0
  17. package/dist/ast-analysis/visitors/cfg-conditionals.js.map +1 -0
  18. package/dist/ast-analysis/visitors/cfg-loops.d.ts +7 -0
  19. package/dist/ast-analysis/visitors/cfg-loops.d.ts.map +1 -0
  20. package/dist/ast-analysis/visitors/cfg-loops.js +73 -0
  21. package/dist/ast-analysis/visitors/cfg-loops.js.map +1 -0
  22. package/dist/ast-analysis/visitors/cfg-shared.d.ts +56 -0
  23. package/dist/ast-analysis/visitors/cfg-shared.d.ts.map +1 -0
  24. package/dist/ast-analysis/visitors/cfg-shared.js +107 -0
  25. package/dist/ast-analysis/visitors/cfg-shared.js.map +1 -0
  26. package/dist/ast-analysis/visitors/cfg-try-catch.d.ts +4 -0
  27. package/dist/ast-analysis/visitors/cfg-try-catch.d.ts.map +1 -0
  28. package/dist/ast-analysis/visitors/cfg-try-catch.js +100 -0
  29. package/dist/ast-analysis/visitors/cfg-try-catch.js.map +1 -0
  30. package/dist/ast-analysis/visitors/cfg-visitor.d.ts +2 -2
  31. package/dist/ast-analysis/visitors/cfg-visitor.d.ts.map +1 -1
  32. package/dist/ast-analysis/visitors/cfg-visitor.js +11 -445
  33. package/dist/ast-analysis/visitors/cfg-visitor.js.map +1 -1
  34. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  35. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  36. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  37. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  38. package/dist/cli/commands/batch.d.ts.map +1 -1
  39. package/dist/cli/commands/batch.js +4 -3
  40. package/dist/cli/commands/batch.js.map +1 -1
  41. package/dist/cli/commands/branch-compare.js +1 -1
  42. package/dist/cli/commands/branch-compare.js.map +1 -1
  43. package/dist/cli/commands/build.js +1 -1
  44. package/dist/cli/commands/build.js.map +1 -1
  45. package/dist/cli/commands/info.d.ts.map +1 -1
  46. package/dist/cli/commands/info.js +1 -2
  47. package/dist/cli/commands/info.js.map +1 -1
  48. package/dist/cli/commands/path.d.ts.map +1 -1
  49. package/dist/cli/commands/path.js +7 -2
  50. package/dist/cli/commands/path.js.map +1 -1
  51. package/dist/cli/commands/plot.d.ts.map +1 -1
  52. package/dist/cli/commands/plot.js +2 -2
  53. package/dist/cli/commands/plot.js.map +1 -1
  54. package/dist/cli/commands/watch.js +1 -1
  55. package/dist/cli/commands/watch.js.map +1 -1
  56. package/dist/cli/index.js +2 -2
  57. package/dist/cli/index.js.map +1 -1
  58. package/dist/cli/shared/open-graph.d.ts +2 -2
  59. package/dist/cli/shared/open-graph.d.ts.map +1 -1
  60. package/dist/cli/shared/open-graph.js.map +1 -1
  61. package/dist/cli/types.d.ts +1 -1
  62. package/dist/cli/types.d.ts.map +1 -1
  63. package/dist/cli.js +2 -3
  64. package/dist/cli.js.map +1 -1
  65. package/dist/db/better-sqlite3.d.ts +3 -0
  66. package/dist/db/better-sqlite3.d.ts.map +1 -0
  67. package/dist/db/better-sqlite3.js +19 -0
  68. package/dist/db/better-sqlite3.js.map +1 -0
  69. package/dist/db/connection.d.ts +30 -2
  70. package/dist/db/connection.d.ts.map +1 -1
  71. package/dist/db/connection.js +167 -4
  72. package/dist/db/connection.js.map +1 -1
  73. package/dist/db/index.d.ts +2 -2
  74. package/dist/db/index.d.ts.map +1 -1
  75. package/dist/db/index.js +1 -1
  76. package/dist/db/index.js.map +1 -1
  77. package/dist/db/migrations.d.ts.map +1 -1
  78. package/dist/db/migrations.js +9 -0
  79. package/dist/db/migrations.js.map +1 -1
  80. package/dist/db/query-builder.d.ts +5 -5
  81. package/dist/db/query-builder.d.ts.map +1 -1
  82. package/dist/db/query-builder.js +20 -4
  83. package/dist/db/query-builder.js.map +1 -1
  84. package/dist/db/repository/index.d.ts +1 -0
  85. package/dist/db/repository/index.d.ts.map +1 -1
  86. package/dist/db/repository/index.js +1 -0
  87. package/dist/db/repository/index.js.map +1 -1
  88. package/dist/db/repository/native-repository.d.ts +58 -0
  89. package/dist/db/repository/native-repository.d.ts.map +1 -0
  90. package/dist/db/repository/native-repository.js +261 -0
  91. package/dist/db/repository/native-repository.js.map +1 -0
  92. package/dist/db/repository/nodes.d.ts +4 -4
  93. package/dist/db/repository/nodes.d.ts.map +1 -1
  94. package/dist/db/repository/nodes.js +6 -6
  95. package/dist/db/repository/nodes.js.map +1 -1
  96. package/dist/domain/analysis/brief.d.ts.map +1 -1
  97. package/dist/domain/analysis/brief.js +1 -3
  98. package/dist/domain/analysis/brief.js.map +1 -1
  99. package/dist/domain/analysis/context.d.ts.map +1 -1
  100. package/dist/domain/analysis/context.js +2 -4
  101. package/dist/domain/analysis/context.js.map +1 -1
  102. package/dist/domain/analysis/dependencies.d.ts +49 -0
  103. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  104. package/dist/domain/analysis/dependencies.js +145 -0
  105. package/dist/domain/analysis/dependencies.js.map +1 -1
  106. package/dist/domain/analysis/diff-impact.d.ts +76 -0
  107. package/dist/domain/analysis/diff-impact.d.ts.map +1 -0
  108. package/dist/domain/analysis/diff-impact.js +282 -0
  109. package/dist/domain/analysis/diff-impact.js.map +1 -0
  110. package/dist/domain/analysis/exports.d.ts.map +1 -1
  111. package/dist/domain/analysis/exports.js +0 -1
  112. package/dist/domain/analysis/exports.js.map +1 -1
  113. package/dist/domain/analysis/fn-impact.d.ts +66 -0
  114. package/dist/domain/analysis/fn-impact.d.ts.map +1 -0
  115. package/dist/domain/analysis/fn-impact.js +189 -0
  116. package/dist/domain/analysis/fn-impact.js.map +1 -0
  117. package/dist/domain/analysis/impact.d.ts +8 -148
  118. package/dist/domain/analysis/impact.d.ts.map +1 -1
  119. package/dist/domain/analysis/impact.js +8 -568
  120. package/dist/domain/analysis/impact.js.map +1 -1
  121. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  122. package/dist/domain/analysis/module-map.js +1 -3
  123. package/dist/domain/analysis/module-map.js.map +1 -1
  124. package/dist/domain/graph/builder/context.d.ts +3 -3
  125. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  126. package/dist/domain/graph/builder/context.js +1 -0
  127. package/dist/domain/graph/builder/context.js.map +1 -1
  128. package/dist/domain/graph/builder/helpers.d.ts +4 -5
  129. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  130. package/dist/domain/graph/builder/helpers.js +1 -2
  131. package/dist/domain/graph/builder/helpers.js.map +1 -1
  132. package/dist/domain/graph/builder/incremental.d.ts +2 -3
  133. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  134. package/dist/domain/graph/builder/incremental.js.map +1 -1
  135. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  136. package/dist/domain/graph/builder/pipeline.js +34 -6
  137. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  138. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  139. package/dist/domain/graph/builder/stages/build-edges.js +113 -15
  140. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  141. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  142. package/dist/domain/graph/builder/stages/build-structure.js +186 -62
  143. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  144. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  145. package/dist/domain/graph/builder/stages/collect-files.js +71 -7
  146. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  147. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  148. package/dist/domain/graph/builder/stages/detect-changes.js +42 -20
  149. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  150. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  151. package/dist/domain/graph/builder/stages/finalize.js +111 -64
  152. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  153. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  154. package/dist/domain/graph/builder/stages/insert-nodes.js +104 -9
  155. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  156. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  157. package/dist/domain/graph/builder/stages/resolve-imports.js +58 -11
  158. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  159. package/dist/domain/graph/cycles.js +2 -2
  160. package/dist/domain/graph/cycles.js.map +1 -1
  161. package/dist/domain/graph/resolve.d.ts.map +1 -1
  162. package/dist/domain/graph/resolve.js +10 -8
  163. package/dist/domain/graph/resolve.js.map +1 -1
  164. package/dist/domain/graph/watcher.d.ts.map +1 -1
  165. package/dist/domain/graph/watcher.js +1 -3
  166. package/dist/domain/graph/watcher.js.map +1 -1
  167. package/dist/domain/parser.d.ts.map +1 -1
  168. package/dist/domain/parser.js +12 -12
  169. package/dist/domain/parser.js.map +1 -1
  170. package/dist/domain/queries.d.ts +3 -2
  171. package/dist/domain/queries.d.ts.map +1 -1
  172. package/dist/domain/queries.js +3 -2
  173. package/dist/domain/queries.js.map +1 -1
  174. package/dist/domain/search/generator.d.ts.map +1 -1
  175. package/dist/domain/search/generator.js.map +1 -1
  176. package/dist/extractors/csharp.js +2 -2
  177. package/dist/extractors/csharp.js.map +1 -1
  178. package/dist/extractors/go.js +2 -2
  179. package/dist/extractors/go.js.map +1 -1
  180. package/dist/extractors/helpers.d.ts +5 -0
  181. package/dist/extractors/helpers.d.ts.map +1 -1
  182. package/dist/extractors/helpers.js +5 -0
  183. package/dist/extractors/helpers.js.map +1 -1
  184. package/dist/extractors/javascript.js +111 -98
  185. package/dist/extractors/javascript.js.map +1 -1
  186. package/dist/extractors/php.js +2 -2
  187. package/dist/extractors/php.js.map +1 -1
  188. package/dist/extractors/python.js +2 -2
  189. package/dist/extractors/python.js.map +1 -1
  190. package/dist/extractors/rust.js +4 -3
  191. package/dist/extractors/rust.js.map +1 -1
  192. package/dist/features/ast.d.ts +14 -1
  193. package/dist/features/ast.d.ts.map +1 -1
  194. package/dist/features/ast.js +38 -1
  195. package/dist/features/ast.js.map +1 -1
  196. package/dist/features/audit.d.ts.map +1 -1
  197. package/dist/features/audit.js +1 -2
  198. package/dist/features/audit.js.map +1 -1
  199. package/dist/features/branch-compare.d.ts.map +1 -1
  200. package/dist/features/branch-compare.js +5 -4
  201. package/dist/features/branch-compare.js.map +1 -1
  202. package/dist/features/cfg.d.ts.map +1 -1
  203. package/dist/features/cfg.js +2 -4
  204. package/dist/features/cfg.js.map +1 -1
  205. package/dist/features/cochange.js +4 -4
  206. package/dist/features/cochange.js.map +1 -1
  207. package/dist/features/communities.js +4 -4
  208. package/dist/features/communities.js.map +1 -1
  209. package/dist/features/complexity-query.d.ts +37 -0
  210. package/dist/features/complexity-query.d.ts.map +1 -0
  211. package/dist/features/complexity-query.js +263 -0
  212. package/dist/features/complexity-query.js.map +1 -0
  213. package/dist/features/complexity.d.ts +2 -30
  214. package/dist/features/complexity.d.ts.map +1 -1
  215. package/dist/features/complexity.js +7 -261
  216. package/dist/features/complexity.js.map +1 -1
  217. package/dist/features/dataflow.d.ts.map +1 -1
  218. package/dist/features/dataflow.js +8 -24
  219. package/dist/features/dataflow.js.map +1 -1
  220. package/dist/features/export.d.ts +7 -8
  221. package/dist/features/export.d.ts.map +1 -1
  222. package/dist/features/export.js.map +1 -1
  223. package/dist/features/flow.d.ts.map +1 -1
  224. package/dist/features/flow.js.map +1 -1
  225. package/dist/features/graph-enrichment.d.ts.map +1 -1
  226. package/dist/features/graph-enrichment.js +1 -3
  227. package/dist/features/graph-enrichment.js.map +1 -1
  228. package/dist/features/manifesto.js +8 -8
  229. package/dist/features/manifesto.js.map +1 -1
  230. package/dist/features/snapshot.js +2 -2
  231. package/dist/features/snapshot.js.map +1 -1
  232. package/dist/features/structure-query.d.ts +76 -0
  233. package/dist/features/structure-query.d.ts.map +1 -0
  234. package/dist/features/structure-query.js +245 -0
  235. package/dist/features/structure-query.js.map +1 -0
  236. package/dist/features/structure.d.ts +12 -67
  237. package/dist/features/structure.d.ts.map +1 -1
  238. package/dist/features/structure.js +188 -244
  239. package/dist/features/structure.js.map +1 -1
  240. package/dist/features/triage.js +2 -2
  241. package/dist/features/triage.js.map +1 -1
  242. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  243. package/dist/graph/algorithms/leiden/adapter.js +2 -9
  244. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  245. package/dist/graph/classifiers/roles.d.ts +5 -1
  246. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  247. package/dist/graph/classifiers/roles.js +20 -12
  248. package/dist/graph/classifiers/roles.js.map +1 -1
  249. package/dist/index.d.ts +1 -0
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js.map +1 -1
  252. package/dist/infrastructure/config.d.ts.map +1 -1
  253. package/dist/infrastructure/config.js +12 -11
  254. package/dist/infrastructure/config.js.map +1 -1
  255. package/dist/infrastructure/native.d.ts.map +1 -1
  256. package/dist/infrastructure/native.js +7 -3
  257. package/dist/infrastructure/native.js.map +1 -1
  258. package/dist/infrastructure/registry.d.ts.map +1 -1
  259. package/dist/infrastructure/registry.js +1 -1
  260. package/dist/infrastructure/registry.js.map +1 -1
  261. package/dist/infrastructure/update-check.js +3 -3
  262. package/dist/infrastructure/update-check.js.map +1 -1
  263. package/dist/mcp/server.d.ts.map +1 -1
  264. package/dist/mcp/server.js +4 -17
  265. package/dist/mcp/server.js.map +1 -1
  266. package/dist/mcp/tool-registry.d.ts.map +1 -1
  267. package/dist/mcp/tool-registry.js +9 -4
  268. package/dist/mcp/tool-registry.js.map +1 -1
  269. package/dist/mcp/tools/audit.js +1 -1
  270. package/dist/mcp/tools/audit.js.map +1 -1
  271. package/dist/mcp/tools/cfg.js +1 -1
  272. package/dist/mcp/tools/cfg.js.map +1 -1
  273. package/dist/mcp/tools/check.js +2 -2
  274. package/dist/mcp/tools/check.js.map +1 -1
  275. package/dist/mcp/tools/dataflow.js +2 -2
  276. package/dist/mcp/tools/dataflow.js.map +1 -1
  277. package/dist/mcp/tools/export-graph.js +1 -1
  278. package/dist/mcp/tools/export-graph.js.map +1 -1
  279. package/dist/mcp/tools/index.d.ts.map +1 -1
  280. package/dist/mcp/tools/index.js.map +1 -1
  281. package/dist/mcp/tools/path.d.ts +1 -0
  282. package/dist/mcp/tools/path.d.ts.map +1 -1
  283. package/dist/mcp/tools/path.js +9 -0
  284. package/dist/mcp/tools/path.js.map +1 -1
  285. package/dist/mcp/tools/query.js +1 -1
  286. package/dist/mcp/tools/query.js.map +1 -1
  287. package/dist/mcp/tools/semantic-search.js +1 -1
  288. package/dist/mcp/tools/semantic-search.js.map +1 -1
  289. package/dist/mcp/tools/sequence.js +1 -1
  290. package/dist/mcp/tools/sequence.js.map +1 -1
  291. package/dist/mcp/tools/symbol-children.js +1 -1
  292. package/dist/mcp/tools/symbol-children.js.map +1 -1
  293. package/dist/mcp/tools/triage.js +1 -1
  294. package/dist/mcp/tools/triage.js.map +1 -1
  295. package/dist/presentation/audit.d.ts.map +1 -1
  296. package/dist/presentation/audit.js +0 -1
  297. package/dist/presentation/audit.js.map +1 -1
  298. package/dist/presentation/diff-impact-mermaid.d.ts +11 -0
  299. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -0
  300. package/dist/presentation/diff-impact-mermaid.js +105 -0
  301. package/dist/presentation/diff-impact-mermaid.js.map +1 -0
  302. package/dist/presentation/flow.d.ts.map +1 -1
  303. package/dist/presentation/flow.js +0 -2
  304. package/dist/presentation/flow.js.map +1 -1
  305. package/dist/presentation/manifesto.d.ts.map +1 -1
  306. package/dist/presentation/manifesto.js +0 -1
  307. package/dist/presentation/manifesto.js.map +1 -1
  308. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  309. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  310. package/dist/presentation/queries-cli/path.d.ts.map +1 -1
  311. package/dist/presentation/queries-cli/path.js +45 -1
  312. package/dist/presentation/queries-cli/path.js.map +1 -1
  313. package/dist/presentation/result-formatter.d.ts.map +1 -1
  314. package/dist/presentation/result-formatter.js +1 -3
  315. package/dist/presentation/result-formatter.js.map +1 -1
  316. package/dist/presentation/sequence.d.ts.map +1 -1
  317. package/dist/presentation/sequence.js +0 -1
  318. package/dist/presentation/sequence.js.map +1 -1
  319. package/dist/presentation/structure.d.ts.map +1 -1
  320. package/dist/presentation/structure.js.map +1 -1
  321. package/dist/presentation/triage.d.ts.map +1 -1
  322. package/dist/presentation/triage.js +0 -1
  323. package/dist/presentation/triage.js.map +1 -1
  324. package/dist/shared/constants.d.ts +9 -3
  325. package/dist/shared/constants.d.ts.map +1 -1
  326. package/dist/shared/constants.js +6 -3
  327. package/dist/shared/constants.js.map +1 -1
  328. package/dist/shared/errors.d.ts +2 -0
  329. package/dist/shared/errors.d.ts.map +1 -1
  330. package/dist/shared/errors.js +4 -0
  331. package/dist/shared/errors.js.map +1 -1
  332. package/dist/shared/version.d.ts +2 -0
  333. package/dist/shared/version.d.ts.map +1 -0
  334. package/dist/shared/version.js +5 -0
  335. package/dist/shared/version.js.map +1 -0
  336. package/dist/types.d.ts +230 -2
  337. package/dist/types.d.ts.map +1 -1
  338. package/package.json +62 -11
  339. package/src/ast-analysis/engine.ts +3 -9
  340. package/src/ast-analysis/rules/javascript.ts +1 -0
  341. package/src/ast-analysis/shared.ts +0 -1
  342. package/src/ast-analysis/visitors/ast-store-visitor.ts +102 -33
  343. package/src/ast-analysis/visitors/cfg-conditionals.ts +227 -0
  344. package/src/ast-analysis/visitors/cfg-loops.ts +136 -0
  345. package/src/ast-analysis/visitors/cfg-shared.ts +196 -0
  346. package/src/ast-analysis/visitors/cfg-try-catch.ts +142 -0
  347. package/src/ast-analysis/visitors/cfg-visitor.ts +34 -655
  348. package/src/ast-analysis/visitors/complexity-visitor.ts +0 -1
  349. package/src/ast-analysis/visitors/dataflow-visitor.ts +0 -1
  350. package/src/cli/commands/batch.ts +4 -3
  351. package/src/cli/commands/branch-compare.ts +1 -1
  352. package/src/cli/commands/build.ts +1 -1
  353. package/src/cli/commands/info.ts +1 -2
  354. package/src/cli/commands/path.ts +7 -2
  355. package/src/cli/commands/plot.ts +2 -2
  356. package/src/cli/commands/watch.ts +1 -1
  357. package/src/cli/index.ts +2 -2
  358. package/src/cli/shared/open-graph.ts +2 -2
  359. package/src/cli/types.ts +1 -1
  360. package/src/cli.ts +2 -3
  361. package/src/db/better-sqlite3.ts +20 -0
  362. package/src/db/connection.ts +191 -16
  363. package/src/db/index.ts +5 -1
  364. package/src/db/migrations.ts +9 -0
  365. package/src/db/query-builder.ts +30 -5
  366. package/src/db/repository/index.ts +1 -0
  367. package/src/db/repository/native-repository.ts +361 -0
  368. package/src/db/repository/nodes.ts +7 -3
  369. package/src/domain/analysis/brief.ts +0 -1
  370. package/src/domain/analysis/context.ts +2 -6
  371. package/src/domain/analysis/dependencies.ts +165 -0
  372. package/src/domain/analysis/diff-impact.ts +354 -0
  373. package/src/domain/analysis/exports.ts +0 -2
  374. package/src/domain/analysis/fn-impact.ts +241 -0
  375. package/src/domain/analysis/impact.ts +8 -718
  376. package/src/domain/analysis/module-map.ts +1 -5
  377. package/src/domain/graph/builder/context.ts +4 -2
  378. package/src/domain/graph/builder/helpers.ts +14 -11
  379. package/src/domain/graph/builder/incremental.ts +33 -28
  380. package/src/domain/graph/builder/pipeline.ts +37 -5
  381. package/src/domain/graph/builder/stages/build-edges.ts +131 -20
  382. package/src/domain/graph/builder/stages/build-structure.ts +245 -80
  383. package/src/domain/graph/builder/stages/collect-files.ts +84 -7
  384. package/src/domain/graph/builder/stages/detect-changes.ts +49 -32
  385. package/src/domain/graph/builder/stages/finalize.ts +132 -84
  386. package/src/domain/graph/builder/stages/insert-nodes.ts +141 -18
  387. package/src/domain/graph/builder/stages/resolve-imports.ts +75 -10
  388. package/src/domain/graph/cycles.ts +2 -2
  389. package/src/domain/graph/resolve.ts +14 -8
  390. package/src/domain/graph/watcher.ts +2 -4
  391. package/src/domain/parser.ts +12 -13
  392. package/src/domain/queries.ts +2 -2
  393. package/src/domain/search/generator.ts +3 -4
  394. package/src/extractors/csharp.ts +2 -2
  395. package/src/extractors/go.ts +2 -2
  396. package/src/extractors/helpers.ts +6 -0
  397. package/src/extractors/javascript.ts +112 -97
  398. package/src/extractors/php.ts +2 -2
  399. package/src/extractors/python.ts +2 -2
  400. package/src/extractors/rust.ts +4 -3
  401. package/src/features/ast.ts +66 -1
  402. package/src/features/audit.ts +1 -2
  403. package/src/features/branch-compare.ts +6 -10
  404. package/src/features/cfg.ts +2 -4
  405. package/src/features/cochange.ts +4 -4
  406. package/src/features/communities.ts +4 -4
  407. package/src/features/complexity-query.ts +370 -0
  408. package/src/features/complexity.ts +6 -365
  409. package/src/features/dataflow.ts +48 -70
  410. package/src/features/export.ts +12 -16
  411. package/src/features/flow.ts +0 -1
  412. package/src/features/graph-enrichment.ts +1 -3
  413. package/src/features/manifesto.ts +8 -8
  414. package/src/features/snapshot.ts +3 -3
  415. package/src/features/structure-query.ts +387 -0
  416. package/src/features/structure.ts +231 -376
  417. package/src/features/triage.ts +2 -2
  418. package/src/graph/algorithms/leiden/adapter.ts +2 -9
  419. package/src/graph/classifiers/roles.ts +22 -13
  420. package/src/index.ts +1 -0
  421. package/src/infrastructure/config.ts +12 -13
  422. package/src/infrastructure/native.ts +7 -3
  423. package/src/infrastructure/registry.ts +1 -1
  424. package/src/infrastructure/update-check.ts +3 -3
  425. package/src/mcp/server.ts +4 -20
  426. package/src/mcp/tool-registry.ts +11 -4
  427. package/src/mcp/tools/audit.ts +1 -1
  428. package/src/mcp/tools/cfg.ts +1 -1
  429. package/src/mcp/tools/check.ts +2 -2
  430. package/src/mcp/tools/dataflow.ts +2 -2
  431. package/src/mcp/tools/export-graph.ts +1 -1
  432. package/src/mcp/tools/index.ts +0 -1
  433. package/src/mcp/tools/path.ts +10 -0
  434. package/src/mcp/tools/query.ts +1 -1
  435. package/src/mcp/tools/semantic-search.ts +1 -1
  436. package/src/mcp/tools/sequence.ts +1 -1
  437. package/src/mcp/tools/symbol-children.ts +1 -1
  438. package/src/mcp/tools/triage.ts +1 -1
  439. package/src/presentation/audit.ts +0 -1
  440. package/src/presentation/diff-impact-mermaid.ts +127 -0
  441. package/src/presentation/flow.ts +0 -2
  442. package/src/presentation/manifesto.ts +0 -1
  443. package/src/presentation/queries-cli/inspect.ts +0 -1
  444. package/src/presentation/queries-cli/path.ts +71 -1
  445. package/src/presentation/result-formatter.ts +0 -1
  446. package/src/presentation/sequence.ts +0 -1
  447. package/src/presentation/structure.ts +0 -12
  448. package/src/presentation/triage.ts +0 -1
  449. package/src/shared/constants.ts +33 -19
  450. package/src/shared/errors.ts +5 -0
  451. package/src/shared/version.ts +10 -0
  452. package/src/types.ts +277 -10
  453. package/src/vendor.d.ts +0 -39
@@ -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.js';
8
+ import { debug } from '#infrastructure/logger.js';
9
+ import { normalizePath } from '#shared/constants.js';
10
+ import type { ExtractorOutput } from '#types';
11
11
  import type { PipelineContext } from '../context.js';
12
12
  import { readFileSafe } from '../helpers.js';
13
13
 
@@ -32,94 +32,107 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
32
32
  }
33
33
  }
34
34
 
35
- // For incremental builds, load unchanged files from DB for complete structure
36
- if (!isFullBuild) {
37
- const existingFiles = db
38
- .prepare("SELECT DISTINCT file FROM nodes WHERE kind = 'file'")
39
- .all() as Array<{ file: string }>;
40
- const defsByFile = db.prepare(
41
- "SELECT name, kind, line FROM nodes WHERE file = ? AND kind != 'file' AND kind != 'directory'",
42
- );
43
- const importCountByFile = db.prepare(
44
- `SELECT COUNT(DISTINCT n2.file) AS cnt FROM edges e
45
- JOIN nodes n1 ON e.source_id = n1.id
46
- JOIN nodes n2 ON e.target_id = n2.id
47
- WHERE n1.file = ? AND e.kind = 'imports'`,
48
- );
49
- const lineCountByFile = db.prepare(
50
- `SELECT n.name AS file, m.line_count
51
- FROM node_metrics m JOIN nodes n ON m.node_id = n.id
52
- WHERE n.kind = 'file'`,
53
- );
54
- const cachedLineCounts = new Map<string, number>();
55
- for (const row of lineCountByFile.all() as Array<{ file: string; line_count: number }>) {
56
- cachedLineCounts.set(row.file, row.line_count);
57
- }
58
- let loadedFromDb = 0;
59
- for (const { file: relPath } of existingFiles) {
60
- if (!fileSymbols.has(relPath)) {
61
- const importCount =
62
- (importCountByFile.get(relPath) as { cnt: number } | undefined)?.cnt || 0;
63
- fileSymbols.set(relPath, {
64
- definitions: defsByFile.all(relPath),
65
- imports: new Array(importCount) as unknown as ExtractorOutput['imports'],
66
- exports: [],
67
- } as unknown as ExtractorOutput);
68
- loadedFromDb++;
69
- }
70
- if (!ctx.lineCountMap.has(relPath)) {
71
- const cached = cachedLineCounts.get(relPath);
72
- if (cached != null) {
73
- ctx.lineCountMap.set(relPath, cached);
74
- } else {
75
- const absPath = path.join(rootDir, relPath);
76
- try {
77
- const content = readFileSafe(absPath);
78
- ctx.lineCountMap.set(relPath, content.split('\n').length);
79
- } catch {
80
- ctx.lineCountMap.set(relPath, 0);
81
- }
35
+ const changedFileList = isFullBuild ? null : [...allSymbols.keys()];
36
+
37
+ // For small incremental builds on large codebases, use a fast path that
38
+ // updates only the changed files' metrics via targeted SQL instead of
39
+ // loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
40
+ // Gate: ≤5 changed files AND significantly more existing files (>20) to
41
+ // avoid triggering on small test fixtures where directory metrics matter.
42
+ const existingFileCount = !isFullBuild
43
+ ? (
44
+ (ctx.nativeDb
45
+ ? ctx.nativeDb.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
46
+ : db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get()) as {
47
+ c: number;
82
48
  }
83
- }
84
- }
85
- debug(`Structure: ${fileSymbols.size} files (${loadedFromDb} loaded from DB)`);
49
+ ).c
50
+ : 0;
51
+ const useSmallIncrementalFastPath =
52
+ !isFullBuild &&
53
+ changedFileList != null &&
54
+ changedFileList.length <= 5 &&
55
+ existingFileCount > 20;
56
+
57
+ if (!isFullBuild && !useSmallIncrementalFastPath) {
58
+ // Medium/large incremental: load unchanged files from DB for complete structure
59
+ loadUnchangedFilesFromDb(ctx);
86
60
  }
87
61
 
88
62
  // Build directory structure
89
63
  const t0 = performance.now();
90
- const relDirs = new Set<string>();
91
- for (const absDir of discoveredDirs) {
92
- relDirs.add(normalizePath(path.relative(rootDir, absDir)));
93
- }
94
- try {
95
- const { buildStructure: buildStructureFn } = (await import(
96
- '../../../../features/structure.js'
97
- )) as {
98
- buildStructure: (
99
- db: PipelineContext['db'],
100
- fileSymbols: Map<string, ExtractorOutput>,
101
- rootDir: string,
102
- lineCountMap: Map<string, number>,
103
- directories: Set<string>,
104
- changedFiles: string[] | null,
105
- ) => void;
106
- };
107
- const changedFilePaths = isFullBuild ? null : [...allSymbols.keys()];
108
- buildStructureFn(db, fileSymbols, rootDir, ctx.lineCountMap, relDirs, changedFilePaths);
109
- } catch (err) {
110
- debug(`Structure analysis failed: ${(err as Error).message}`);
64
+ if (useSmallIncrementalFastPath) {
65
+ updateChangedFileMetrics(ctx, changedFileList!);
66
+ } else {
67
+ const relDirs = new Set<string>();
68
+ for (const absDir of discoveredDirs) {
69
+ relDirs.add(normalizePath(path.relative(rootDir, absDir)));
70
+ }
71
+ try {
72
+ const { buildStructure: buildStructureFn } = (await import(
73
+ '../../../../features/structure.js'
74
+ )) as {
75
+ buildStructure: (
76
+ db: PipelineContext['db'],
77
+ fileSymbols: Map<string, ExtractorOutput>,
78
+ rootDir: string,
79
+ lineCountMap: Map<string, number>,
80
+ directories: Set<string>,
81
+ changedFiles: string[] | null,
82
+ ) => void;
83
+ };
84
+ const changedFilePaths = isFullBuild ? null : [...allSymbols.keys()];
85
+ buildStructureFn(db, fileSymbols, rootDir, ctx.lineCountMap, relDirs, changedFilePaths);
86
+ } catch (err) {
87
+ debug(`Structure analysis failed: ${(err as Error).message}`);
88
+ }
111
89
  }
112
90
  ctx.timing.structureMs = performance.now() - t0;
113
91
 
114
- // Classify node roles
92
+ // Classify node roles (incremental: only reclassify changed files' nodes)
115
93
  const t1 = performance.now();
116
94
  try {
117
- const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
118
- classifyNodeRoles: (db: PipelineContext['db']) => Record<string, number>;
119
- };
120
- const roleSummary = classifyNodeRoles(db);
95
+ let roleSummary: Record<string, number> | null = null;
96
+
97
+ // Use NativeDatabase persistent connection (Phase 6.15+).
98
+ // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
99
+ if (ctx.nativeDb?.classifyRolesFull) {
100
+ const nativeResult =
101
+ changedFileList && changedFileList.length > 0
102
+ ? ctx.nativeDb.classifyRolesIncremental(changedFileList)
103
+ : ctx.nativeDb.classifyRolesFull();
104
+ if (nativeResult) {
105
+ roleSummary = {
106
+ entry: nativeResult.entry,
107
+ core: nativeResult.core,
108
+ utility: nativeResult.utility,
109
+ adapter: nativeResult.adapter,
110
+ dead: nativeResult.dead,
111
+ 'dead-leaf': nativeResult.deadLeaf,
112
+ 'dead-entry': nativeResult.deadEntry,
113
+ 'dead-ffi': nativeResult.deadFfi,
114
+ 'dead-unresolved': nativeResult.deadUnresolved,
115
+ 'test-only': nativeResult.testOnly,
116
+ leaf: nativeResult.leaf,
117
+ };
118
+ }
119
+ }
120
+
121
+ // Fall back to JS path
122
+ if (!roleSummary) {
123
+ const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
124
+ classifyNodeRoles: (
125
+ db: PipelineContext['db'],
126
+ changedFiles?: string[] | null,
127
+ ) => Record<string, number>;
128
+ };
129
+ roleSummary = classifyNodeRoles(db, changedFileList);
130
+ }
131
+
121
132
  debug(
122
- `Roles: ${Object.entries(roleSummary)
133
+ `Roles${changedFileList ? ` (incremental, ${changedFileList.length} files)` : ''}: ${Object.entries(
134
+ roleSummary,
135
+ )
123
136
  .map(([r, c]) => `${r}=${c}`)
124
137
  .join(', ')}`,
125
138
  );
@@ -128,3 +141,155 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
128
141
  }
129
142
  ctx.timing.rolesMs = performance.now() - t1;
130
143
  }
144
+
145
+ // ── Small incremental fast path ──────────────────────────────────────────
146
+
147
+ /**
148
+ * For small incremental builds, update only the changed files' node_metrics
149
+ * using targeted SQL queries. Skips the full DB load of all definitions
150
+ * (~8ms) and full structure rebuild (~15ms), replacing them with per-file
151
+ * indexed queries (~1-2ms total for 1-5 files).
152
+ *
153
+ * Directory metrics are not recomputed — a 1-5 file change won't
154
+ * meaningfully alter directory-level cohesion or symbol counts.
155
+ */
156
+ function updateChangedFileMetrics(ctx: PipelineContext, changedFiles: string[]): void {
157
+ const { db } = ctx;
158
+
159
+ const getFileNodeId = db.prepare(
160
+ "SELECT id FROM nodes WHERE name = ? AND kind = 'file' AND file = ? AND line = 0",
161
+ );
162
+ const getSymbolCount = db.prepare(
163
+ "SELECT COUNT(*) as c FROM nodes WHERE file = ? AND kind != 'file' AND kind != 'directory'",
164
+ );
165
+ const getImportCount = db.prepare(`
166
+ SELECT COUNT(DISTINCT n2.file) AS cnt FROM edges e
167
+ JOIN nodes n1 ON e.source_id = n1.id
168
+ JOIN nodes n2 ON e.target_id = n2.id
169
+ WHERE e.kind = 'imports' AND n1.file = ?
170
+ `);
171
+ const getFanIn = db.prepare(`
172
+ SELECT COUNT(DISTINCT n_src.file) AS cnt FROM edges e
173
+ JOIN nodes n_src ON e.source_id = n_src.id
174
+ JOIN nodes n_tgt ON e.target_id = n_tgt.id
175
+ WHERE e.kind = 'imports' AND n_tgt.file = ? AND n_src.file != n_tgt.file
176
+ `);
177
+ const getFanOut = db.prepare(`
178
+ SELECT COUNT(DISTINCT n_tgt.file) AS cnt FROM edges e
179
+ JOIN nodes n_src ON e.source_id = n_src.id
180
+ JOIN nodes n_tgt ON e.target_id = n_tgt.id
181
+ WHERE e.kind = 'imports' AND n_src.file = ? AND n_src.file != n_tgt.file
182
+ `);
183
+ const upsertMetric = db.prepare(`
184
+ INSERT OR REPLACE INTO node_metrics
185
+ (node_id, line_count, symbol_count, import_count, export_count, fan_in, fan_out, cohesion, file_count)
186
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
187
+ `);
188
+
189
+ db.transaction(() => {
190
+ for (const relPath of changedFiles) {
191
+ const fileRow = getFileNodeId.get(relPath, relPath) as { id: number } | undefined;
192
+ if (!fileRow) continue;
193
+
194
+ const lineCount = ctx.lineCountMap.get(relPath) || 0;
195
+ const symbolCount = (getSymbolCount.get(relPath) as { c: number }).c;
196
+ const importCount = (getImportCount.get(relPath) as { cnt: number }).cnt;
197
+ const exportCount = ctx.fileSymbols.get(relPath)?.exports.length || 0;
198
+ const fanIn = (getFanIn.get(relPath) as { cnt: number }).cnt;
199
+ const fanOut = (getFanOut.get(relPath) as { cnt: number }).cnt;
200
+
201
+ upsertMetric.run(
202
+ fileRow.id,
203
+ lineCount,
204
+ symbolCount,
205
+ importCount,
206
+ exportCount,
207
+ fanIn,
208
+ fanOut,
209
+ null,
210
+ null,
211
+ );
212
+ }
213
+ })();
214
+
215
+ debug(`Structure (fast path): updated metrics for ${changedFiles.length} files`);
216
+ }
217
+
218
+ // ── Full incremental DB load (medium/large changes) ──────────────────────
219
+
220
+ function loadUnchangedFilesFromDb(ctx: PipelineContext): void {
221
+ const { db, fileSymbols, rootDir } = ctx;
222
+
223
+ const existingFiles = db
224
+ .prepare("SELECT DISTINCT file FROM nodes WHERE kind = 'file'")
225
+ .all() as Array<{ file: string }>;
226
+
227
+ // Batch load: all definitions, import counts, and line counts in single queries
228
+ const allDefs = db
229
+ .prepare(
230
+ "SELECT file, name, kind, line FROM nodes WHERE kind != 'file' AND kind != 'directory'",
231
+ )
232
+ .all() as Array<{ file: string; name: string; kind: string; line: number }>;
233
+ const defsByFileMap = new Map<string, Array<{ name: string; kind: string; line: number }>>();
234
+ for (const row of allDefs) {
235
+ let arr = defsByFileMap.get(row.file);
236
+ if (!arr) {
237
+ arr = [];
238
+ defsByFileMap.set(row.file, arr);
239
+ }
240
+ arr.push({ name: row.name, kind: row.kind, line: row.line });
241
+ }
242
+
243
+ const allImportCounts = db
244
+ .prepare(
245
+ `SELECT n1.file, COUNT(DISTINCT n2.file) AS cnt FROM edges e
246
+ JOIN nodes n1 ON e.source_id = n1.id
247
+ JOIN nodes n2 ON e.target_id = n2.id
248
+ WHERE e.kind = 'imports'
249
+ GROUP BY n1.file`,
250
+ )
251
+ .all() as Array<{ file: string; cnt: number }>;
252
+ const importCountMap = new Map<string, number>();
253
+ for (const row of allImportCounts) {
254
+ importCountMap.set(row.file, row.cnt);
255
+ }
256
+
257
+ const cachedLineCounts = new Map<string, number>();
258
+ for (const row of db
259
+ .prepare(
260
+ `SELECT n.name AS file, m.line_count
261
+ FROM node_metrics m JOIN nodes n ON m.node_id = n.id
262
+ WHERE n.kind = 'file'`,
263
+ )
264
+ .all() as Array<{ file: string; line_count: number }>) {
265
+ cachedLineCounts.set(row.file, row.line_count);
266
+ }
267
+
268
+ let loadedFromDb = 0;
269
+ for (const { file: relPath } of existingFiles) {
270
+ if (!fileSymbols.has(relPath)) {
271
+ const importCount = importCountMap.get(relPath) || 0;
272
+ fileSymbols.set(relPath, {
273
+ definitions: defsByFileMap.get(relPath) || [],
274
+ imports: new Array(importCount) as unknown as ExtractorOutput['imports'],
275
+ exports: [],
276
+ } as unknown as ExtractorOutput);
277
+ loadedFromDb++;
278
+ }
279
+ if (!ctx.lineCountMap.has(relPath)) {
280
+ const cached = cachedLineCounts.get(relPath);
281
+ if (cached != null) {
282
+ ctx.lineCountMap.set(relPath, cached);
283
+ } else {
284
+ const absPath = path.join(rootDir, relPath);
285
+ try {
286
+ const content = readFileSafe(absPath);
287
+ ctx.lineCountMap.set(relPath, content.split('\n').length);
288
+ } catch {
289
+ ctx.lineCountMap.set(relPath, 0);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ debug(`Structure: ${fileSymbols.size} files (${loadedFromDb} loaded from DB)`);
295
+ }
@@ -2,14 +2,78 @@
2
2
  * Stage: collectFiles
3
3
  *
4
4
  * Collects all source files to process. Handles both normal and scoped rebuilds.
5
+ * For incremental builds with a valid journal, reconstructs the file list from
6
+ * the DB's file_hashes table + journal deltas, skipping the filesystem scan.
5
7
  */
6
8
  import fs from 'node:fs';
7
9
  import path from 'node:path';
8
- import { info } from '../../../../infrastructure/logger.js';
9
- import { normalizePath } from '../../../../shared/constants.js';
10
+ import { debug, info } from '#infrastructure/logger.js';
11
+ import { normalizePath } from '#shared/constants.js';
12
+ import { readJournal } from '../../journal.js';
10
13
  import type { PipelineContext } from '../context.js';
11
14
  import { collectFiles as collectFilesUtil } from '../helpers.js';
12
15
 
16
+ /**
17
+ * Reconstruct allFiles from DB file_hashes + journal deltas.
18
+ * Returns null when the fast path isn't applicable (first build, no journal, etc).
19
+ */
20
+ function tryFastCollect(
21
+ ctx: PipelineContext,
22
+ ): { files: string[]; directories: Set<string> } | null {
23
+ const { db, rootDir } = ctx;
24
+
25
+ // 1. Check that file_hashes table exists and has entries
26
+ let dbFileCount: number;
27
+ try {
28
+ dbFileCount = (db.prepare('SELECT COUNT(*) as c FROM file_hashes').get() as { c: number }).c;
29
+ } catch {
30
+ return null;
31
+ }
32
+ if (dbFileCount === 0) return null;
33
+
34
+ // 2. Read the journal — only use fast path when journal has entries,
35
+ // proving the watcher was active and tracking changes. An empty-but-valid
36
+ // journal (no watcher) could miss file deletions.
37
+ const journal = readJournal(rootDir);
38
+ if (!journal.valid) return null;
39
+ const hasEntries =
40
+ (journal.changed && journal.changed.length > 0) ||
41
+ (journal.removed && journal.removed.length > 0);
42
+ if (!hasEntries) return null;
43
+
44
+ // 3. Load existing file list from file_hashes (relative paths)
45
+ const dbFiles = (db.prepare('SELECT file FROM file_hashes').all() as Array<{ file: string }>).map(
46
+ (r) => r.file,
47
+ );
48
+
49
+ // 4. Apply journal deltas: remove deleted files, add new/changed files
50
+ const fileSet = new Set(dbFiles);
51
+ if (journal.removed) {
52
+ for (const removed of journal.removed) {
53
+ fileSet.delete(removed);
54
+ }
55
+ }
56
+ if (journal.changed) {
57
+ for (const changed of journal.changed) {
58
+ fileSet.add(changed);
59
+ }
60
+ }
61
+
62
+ // 5. Convert to absolute paths and compute directories
63
+ const files: string[] = [];
64
+ const directories = new Set<string>();
65
+ for (const relPath of fileSet) {
66
+ const absPath = path.join(rootDir, relPath);
67
+ files.push(absPath);
68
+ directories.add(path.dirname(absPath));
69
+ }
70
+
71
+ debug(
72
+ `collectFiles fast path: ${dbFiles.length} from DB, journal: +${journal.changed?.length ?? 0}/-${journal.removed?.length ?? 0} → ${files.length} files`,
73
+ );
74
+ return { files, directories };
75
+ }
76
+
13
77
  export async function collectFiles(ctx: PipelineContext): Promise<void> {
14
78
  const { rootDir, config, opts } = ctx;
15
79
 
@@ -33,10 +97,23 @@ export async function collectFiles(ctx: PipelineContext): Promise<void> {
33
97
  ctx.removed = missing;
34
98
  ctx.isFullBuild = false;
35
99
  info(`Scoped rebuild: ${existing.length} files to rebuild, ${missing.length} to purge`);
36
- } else {
37
- const collected = collectFilesUtil(rootDir, [], config, new Set<string>());
38
- ctx.allFiles = collected.files;
39
- ctx.discoveredDirs = collected.directories;
40
- info(`Found ${ctx.allFiles.length} files to parse`);
100
+ return;
41
101
  }
102
+
103
+ // Incremental fast path: reconstruct file list from DB + journal deltas
104
+ // instead of full recursive filesystem scan (~8ms savings on 473 files).
105
+ if (ctx.incremental && !ctx.forceFullRebuild) {
106
+ const fast = tryFastCollect(ctx);
107
+ if (fast) {
108
+ ctx.allFiles = fast.files;
109
+ ctx.discoveredDirs = fast.directories;
110
+ info(`Found ${ctx.allFiles.length} files (cached)`);
111
+ return;
112
+ }
113
+ }
114
+
115
+ const collected = collectFilesUtil(rootDir, [], config, new Set<string>());
116
+ ctx.allFiles = collected.files;
117
+ ctx.discoveredDirs = collected.directories;
118
+ info(`Found ${ctx.allFiles.length} files to parse`);
42
119
  }
@@ -7,11 +7,10 @@
7
7
  */
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
- import type BetterSqlite3 from 'better-sqlite3';
11
10
  import { closeDb } from '../../../../db/index.js';
12
11
  import { debug, info } from '../../../../infrastructure/logger.js';
13
12
  import { normalizePath } from '../../../../shared/constants.js';
14
- import type { EngineOpts, ExtractorOutput } from '../../../../types.js';
13
+ import type { BetterSqlite3Database, ExtractorOutput, NativeDatabase } from '../../../../types.js';
15
14
  import { parseFilesAuto } from '../../../parser.js';
16
15
  import { readJournal, writeJournalHeader } from '../../journal.js';
17
16
  import type { PipelineContext } from '../context.js';
@@ -56,13 +55,19 @@ interface NeedsHashItem {
56
55
  // ── Helpers ────────────────────────────────────────────────────────────
57
56
 
58
57
  function getChangedFiles(
59
- db: BetterSqlite3.Database,
58
+ db: BetterSqlite3Database,
60
59
  allFiles: string[],
61
60
  rootDir: string,
61
+ nativeDb?: NativeDatabase,
62
62
  ): ChangeResult {
63
63
  let hasTable = false;
64
64
  try {
65
- db.prepare('SELECT 1 FROM file_hashes LIMIT 1').get();
65
+ if (nativeDb) {
66
+ nativeDb.queryGet('SELECT 1 FROM file_hashes LIMIT 1', []);
67
+ } else {
68
+ db.prepare('SELECT 1 FROM file_hashes LIMIT 1').get();
69
+ }
70
+ // Query succeeded → table exists (result may be undefined if table is empty)
66
71
  hasTable = true;
67
72
  } catch {
68
73
  /* table doesn't exist */
@@ -76,11 +81,11 @@ function getChangedFiles(
76
81
  };
77
82
  }
78
83
 
79
- const existing = new Map<string, FileHashRow>(
80
- (db.prepare('SELECT file, hash, mtime, size FROM file_hashes').all() as FileHashRow[]).map(
81
- (r) => [r.file, r],
82
- ),
83
- );
84
+ const sql = 'SELECT file, hash, mtime, size FROM file_hashes';
85
+ const rows = nativeDb
86
+ ? (nativeDb.queryAll(sql, []) as unknown as FileHashRow[])
87
+ : (db.prepare(sql).all() as FileHashRow[]);
88
+ const existing = new Map<string, FileHashRow>(rows.map((r) => [r.file, r]));
84
89
 
85
90
  const removed = detectRemovedFiles(existing, allFiles, rootDir);
86
91
  const journalResult = tryJournalTier(db, existing, rootDir, removed);
@@ -107,7 +112,7 @@ function detectRemovedFiles(
107
112
  }
108
113
 
109
114
  function tryJournalTier(
110
- db: BetterSqlite3.Database,
115
+ db: BetterSqlite3Database,
111
116
  existing: Map<string, FileHashRow>,
112
117
  rootDir: string,
113
118
  removed: string[],
@@ -227,7 +232,7 @@ function mtimeAndHashTiers(
227
232
  async function runPendingAnalysis(ctx: PipelineContext): Promise<boolean> {
228
233
  const { db, opts, engineOpts, allFiles, rootDir } = ctx;
229
234
  const needsCfg =
230
- (opts as Record<string, unknown>)['cfg'] !== false &&
235
+ (opts as Record<string, unknown>).cfg !== false &&
231
236
  (() => {
232
237
  try {
233
238
  return (
@@ -239,7 +244,7 @@ async function runPendingAnalysis(ctx: PipelineContext): Promise<boolean> {
239
244
  }
240
245
  })();
241
246
  const needsDataflow =
242
- (opts as Record<string, unknown>)['dataflow'] !== false &&
247
+ (opts as Record<string, unknown>).dataflow !== false &&
243
248
  (() => {
244
249
  try {
245
250
  return (
@@ -255,7 +260,7 @@ async function runPendingAnalysis(ctx: PipelineContext): Promise<boolean> {
255
260
  info('No file changes. Running pending analysis pass...');
256
261
  const analysisOpts = {
257
262
  ...engineOpts,
258
- dataflow: needsDataflow && (opts as Record<string, unknown>)['dataflow'] !== false,
263
+ dataflow: needsDataflow && (opts as Record<string, unknown>).dataflow !== false,
259
264
  };
260
265
  const analysisSymbols: Map<string, ExtractorOutput> = await parseFilesAuto(
261
266
  allFiles,
@@ -295,7 +300,7 @@ function healMetadata(ctx: PipelineContext): void {
295
300
  }
296
301
 
297
302
  function findReverseDependencies(
298
- db: BetterSqlite3.Database,
303
+ db: BetterSqlite3Database,
299
304
  changedRelPaths: Set<string>,
300
305
  rootDir: string,
301
306
  ): Set<string> {
@@ -326,24 +331,36 @@ function purgeAndAddReverseDeps(
326
331
  reverseDeps: Set<string>,
327
332
  ): void {
328
333
  const { db, rootDir } = ctx;
329
- if (changePaths.length > 0 || ctx.removed.length > 0) {
330
- purgeFilesFromGraph(db, [...ctx.removed, ...changePaths], { purgeHashes: false });
331
- }
332
- if (reverseDeps.size > 0) {
333
- const deleteOutgoingEdgesForFile = db.prepare(
334
- 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
335
- );
336
- for (const relPath of reverseDeps) {
337
- deleteOutgoingEdgesForFile.run(relPath);
338
- }
339
- for (const relPath of reverseDeps) {
340
- const absPath = path.join(rootDir, relPath);
341
- ctx.parseChanges.push({ file: absPath, relPath, _reverseDepOnly: true });
334
+ const hasPurge = changePaths.length > 0 || ctx.removed.length > 0;
335
+ const hasReverseDeps = reverseDeps.size > 0;
336
+ const reverseDepList = hasReverseDeps ? [...reverseDeps] : [];
337
+
338
+ if (hasPurge || hasReverseDeps) {
339
+ const filesToPurge = hasPurge ? [...ctx.removed, ...changePaths] : [];
340
+ // Prefer NativeDatabase: purge + reverse-dep edge deletion in one transaction (#670)
341
+ if (ctx.nativeDb?.purgeFilesData) {
342
+ ctx.nativeDb.purgeFilesData(filesToPurge, false, hasReverseDeps ? reverseDepList : undefined);
343
+ } else {
344
+ if (hasPurge) {
345
+ purgeFilesFromGraph(db, filesToPurge, { purgeHashes: false });
346
+ }
347
+ if (hasReverseDeps) {
348
+ const deleteOutgoingEdgesForFile = db.prepare(
349
+ 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
350
+ );
351
+ for (const relPath of reverseDepList) {
352
+ deleteOutgoingEdgesForFile.run(relPath);
353
+ }
354
+ }
342
355
  }
343
356
  }
357
+ for (const relPath of reverseDeps) {
358
+ const absPath = path.join(rootDir, relPath);
359
+ ctx.parseChanges.push({ file: absPath, relPath, _reverseDepOnly: true });
360
+ }
344
361
  }
345
362
 
346
- function detectHasEmbeddings(db: BetterSqlite3.Database): boolean {
363
+ function detectHasEmbeddings(db: BetterSqlite3Database): boolean {
347
364
  try {
348
365
  db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
349
366
  return true;
@@ -359,7 +376,7 @@ function handleScopedBuild(ctx: PipelineContext): void {
359
376
  (item) => item.relPath || normalizePath(path.relative(rootDir, item.file)),
360
377
  );
361
378
  let reverseDeps = new Set<string>();
362
- if (!(opts as Record<string, unknown>)['noReverseDeps']) {
379
+ if (!(opts as Record<string, unknown>).noReverseDeps) {
363
380
  const changedRelPaths = new Set<string>([...changePaths, ...ctx.removed]);
364
381
  reverseDeps = findReverseDependencies(db, changedRelPaths, rootDir);
365
382
  }
@@ -386,7 +403,7 @@ function handleIncrementalBuild(ctx: PipelineContext): void {
386
403
  const { db, rootDir, opts } = ctx;
387
404
  ctx.hasEmbeddings = detectHasEmbeddings(db);
388
405
  let reverseDeps = new Set<string>();
389
- if (!(opts as Record<string, unknown>)['noReverseDeps']) {
406
+ if (!(opts as Record<string, unknown>).noReverseDeps) {
390
407
  const changedRelPaths = new Set<string>();
391
408
  for (const item of ctx.parseChanges) {
392
409
  changedRelPaths.add(item.relPath || normalizePath(path.relative(rootDir, item.file)));
@@ -410,13 +427,13 @@ function handleIncrementalBuild(ctx: PipelineContext): void {
410
427
 
411
428
  export async function detectChanges(ctx: PipelineContext): Promise<void> {
412
429
  const { db, allFiles, rootDir, incremental, forceFullRebuild, opts } = ctx;
413
- if ((opts as Record<string, unknown>)['scope']) {
430
+ if ((opts as Record<string, unknown>).scope) {
414
431
  handleScopedBuild(ctx);
415
432
  return;
416
433
  }
417
434
  const increResult =
418
435
  incremental && !forceFullRebuild
419
- ? getChangedFiles(db, allFiles, rootDir)
436
+ ? getChangedFiles(db, allFiles, rootDir, ctx.nativeDb)
420
437
  : {
421
438
  changed: allFiles.map((f): ChangedFile => ({ file: f })),
422
439
  removed: [] as string[],