@optave/codegraph 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/README.md +47 -21
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +206 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/dart.d.ts +6 -0
  103. package/dist/extractors/dart.d.ts.map +1 -0
  104. package/dist/extractors/dart.js +277 -0
  105. package/dist/extractors/dart.js.map +1 -0
  106. package/dist/extractors/elixir.d.ts +9 -0
  107. package/dist/extractors/elixir.d.ts.map +1 -0
  108. package/dist/extractors/elixir.js +223 -0
  109. package/dist/extractors/elixir.js.map +1 -0
  110. package/dist/extractors/go.d.ts.map +1 -1
  111. package/dist/extractors/go.js +126 -130
  112. package/dist/extractors/go.js.map +1 -1
  113. package/dist/extractors/haskell.d.ts +8 -0
  114. package/dist/extractors/haskell.d.ts.map +1 -0
  115. package/dist/extractors/haskell.js +217 -0
  116. package/dist/extractors/haskell.js.map +1 -0
  117. package/dist/extractors/hcl.js +6 -6
  118. package/dist/extractors/hcl.js.map +1 -1
  119. package/dist/extractors/helpers.d.ts +32 -1
  120. package/dist/extractors/helpers.d.ts.map +1 -1
  121. package/dist/extractors/helpers.js +74 -0
  122. package/dist/extractors/helpers.js.map +1 -1
  123. package/dist/extractors/index.d.ts +12 -0
  124. package/dist/extractors/index.d.ts.map +1 -1
  125. package/dist/extractors/index.js +12 -0
  126. package/dist/extractors/index.js.map +1 -1
  127. package/dist/extractors/java.d.ts.map +1 -1
  128. package/dist/extractors/java.js +32 -47
  129. package/dist/extractors/java.js.map +1 -1
  130. package/dist/extractors/javascript.d.ts.map +1 -1
  131. package/dist/extractors/javascript.js +306 -292
  132. package/dist/extractors/javascript.js.map +1 -1
  133. package/dist/extractors/kotlin.d.ts +6 -0
  134. package/dist/extractors/kotlin.d.ts.map +1 -0
  135. package/dist/extractors/kotlin.js +275 -0
  136. package/dist/extractors/kotlin.js.map +1 -0
  137. package/dist/extractors/lua.d.ts +6 -0
  138. package/dist/extractors/lua.d.ts.map +1 -0
  139. package/dist/extractors/lua.js +162 -0
  140. package/dist/extractors/lua.js.map +1 -0
  141. package/dist/extractors/ocaml.d.ts +6 -0
  142. package/dist/extractors/ocaml.d.ts.map +1 -0
  143. package/dist/extractors/ocaml.js +236 -0
  144. package/dist/extractors/ocaml.js.map +1 -0
  145. package/dist/extractors/php.d.ts.map +1 -1
  146. package/dist/extractors/php.js +39 -44
  147. package/dist/extractors/php.js.map +1 -1
  148. package/dist/extractors/python.d.ts.map +1 -1
  149. package/dist/extractors/python.js +75 -93
  150. package/dist/extractors/python.js.map +1 -1
  151. package/dist/extractors/ruby.js +6 -13
  152. package/dist/extractors/ruby.js.map +1 -1
  153. package/dist/extractors/rust.d.ts.map +1 -1
  154. package/dist/extractors/rust.js +58 -83
  155. package/dist/extractors/rust.js.map +1 -1
  156. package/dist/extractors/scala.d.ts +6 -0
  157. package/dist/extractors/scala.d.ts.map +1 -0
  158. package/dist/extractors/scala.js +269 -0
  159. package/dist/extractors/scala.js.map +1 -0
  160. package/dist/extractors/swift.d.ts +6 -0
  161. package/dist/extractors/swift.d.ts.map +1 -0
  162. package/dist/extractors/swift.js +275 -0
  163. package/dist/extractors/swift.js.map +1 -0
  164. package/dist/extractors/zig.d.ts +9 -0
  165. package/dist/extractors/zig.d.ts.map +1 -0
  166. package/dist/extractors/zig.js +276 -0
  167. package/dist/extractors/zig.js.map +1 -0
  168. package/dist/features/ast.d.ts +2 -0
  169. package/dist/features/ast.d.ts.map +1 -1
  170. package/dist/features/ast.js +9 -24
  171. package/dist/features/ast.js.map +1 -1
  172. package/dist/features/audit.d.ts.map +1 -1
  173. package/dist/features/audit.js +17 -21
  174. package/dist/features/audit.js.map +1 -1
  175. package/dist/features/branch-compare.d.ts.map +1 -1
  176. package/dist/features/branch-compare.js +47 -3
  177. package/dist/features/branch-compare.js.map +1 -1
  178. package/dist/features/cfg.d.ts +7 -1
  179. package/dist/features/cfg.d.ts.map +1 -1
  180. package/dist/features/cfg.js +72 -61
  181. package/dist/features/cfg.js.map +1 -1
  182. package/dist/features/check.d.ts.map +1 -1
  183. package/dist/features/check.js +79 -62
  184. package/dist/features/check.js.map +1 -1
  185. package/dist/features/complexity-query.d.ts.map +1 -1
  186. package/dist/features/complexity-query.js +142 -137
  187. package/dist/features/complexity-query.js.map +1 -1
  188. package/dist/features/complexity.d.ts +7 -1
  189. package/dist/features/complexity.d.ts.map +1 -1
  190. package/dist/features/complexity.js +62 -1
  191. package/dist/features/complexity.js.map +1 -1
  192. package/dist/features/dataflow.d.ts +7 -1
  193. package/dist/features/dataflow.d.ts.map +1 -1
  194. package/dist/features/dataflow.js +356 -188
  195. package/dist/features/dataflow.js.map +1 -1
  196. package/dist/features/graph-enrichment.d.ts.map +1 -1
  197. package/dist/features/graph-enrichment.js +117 -104
  198. package/dist/features/graph-enrichment.js.map +1 -1
  199. package/dist/features/sequence.d.ts.map +1 -1
  200. package/dist/features/sequence.js +25 -4
  201. package/dist/features/sequence.js.map +1 -1
  202. package/dist/features/structure-query.d.ts.map +1 -1
  203. package/dist/features/structure-query.js +29 -4
  204. package/dist/features/structure-query.js.map +1 -1
  205. package/dist/features/structure.d.ts.map +1 -1
  206. package/dist/features/structure.js +35 -15
  207. package/dist/features/structure.js.map +1 -1
  208. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  209. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  210. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  211. package/dist/graph/algorithms/leiden/index.js +43 -28
  212. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  213. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  214. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  215. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  216. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  217. package/dist/graph/algorithms/leiden/partition.js +89 -106
  218. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  219. package/dist/graph/model.d.ts +2 -0
  220. package/dist/graph/model.d.ts.map +1 -1
  221. package/dist/graph/model.js +20 -8
  222. package/dist/graph/model.js.map +1 -1
  223. package/dist/infrastructure/config.d.ts +0 -8
  224. package/dist/infrastructure/config.d.ts.map +1 -1
  225. package/dist/infrastructure/config.js +73 -62
  226. package/dist/infrastructure/config.js.map +1 -1
  227. package/dist/infrastructure/registry.d.ts +0 -8
  228. package/dist/infrastructure/registry.d.ts.map +1 -1
  229. package/dist/infrastructure/registry.js +12 -14
  230. package/dist/infrastructure/registry.js.map +1 -1
  231. package/dist/mcp/server.d.ts.map +1 -1
  232. package/dist/mcp/server.js +45 -36
  233. package/dist/mcp/server.js.map +1 -1
  234. package/dist/presentation/audit.d.ts.map +1 -1
  235. package/dist/presentation/audit.js +61 -57
  236. package/dist/presentation/audit.js.map +1 -1
  237. package/dist/presentation/branch-compare.d.ts.map +1 -1
  238. package/dist/presentation/branch-compare.js +56 -38
  239. package/dist/presentation/branch-compare.js.map +1 -1
  240. package/dist/presentation/check.d.ts.map +1 -1
  241. package/dist/presentation/check.js +30 -32
  242. package/dist/presentation/check.js.map +1 -1
  243. package/dist/presentation/colors.d.ts.map +1 -1
  244. package/dist/presentation/colors.js +2 -0
  245. package/dist/presentation/colors.js.map +1 -1
  246. package/dist/presentation/complexity.d.ts.map +1 -1
  247. package/dist/presentation/complexity.js +25 -19
  248. package/dist/presentation/complexity.js.map +1 -1
  249. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  250. package/dist/presentation/queries-cli/exports.js +15 -15
  251. package/dist/presentation/queries-cli/exports.js.map +1 -1
  252. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  253. package/dist/presentation/queries-cli/impact.js +29 -19
  254. package/dist/presentation/queries-cli/impact.js.map +1 -1
  255. package/dist/types.d.ts +182 -7
  256. package/dist/types.d.ts.map +1 -1
  257. package/grammars/tree-sitter-bash.wasm +0 -0
  258. package/grammars/tree-sitter-c.wasm +0 -0
  259. package/grammars/tree-sitter-cpp.wasm +0 -0
  260. package/grammars/tree-sitter-dart.wasm +0 -0
  261. package/grammars/tree-sitter-elixir.wasm +0 -0
  262. package/grammars/tree-sitter-haskell.wasm +0 -0
  263. package/grammars/tree-sitter-kotlin.wasm +0 -0
  264. package/grammars/tree-sitter-lua.wasm +0 -0
  265. package/grammars/tree-sitter-ocaml.wasm +0 -0
  266. package/grammars/tree-sitter-scala.wasm +0 -0
  267. package/grammars/tree-sitter-swift.wasm +0 -0
  268. package/grammars/tree-sitter-zig.wasm +0 -0
  269. package/package.json +19 -7
  270. package/src/ast-analysis/engine.ts +147 -138
  271. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  272. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  273. package/src/db/connection.ts +90 -59
  274. package/src/db/index.ts +1 -0
  275. package/src/db/migrations.ts +36 -32
  276. package/src/domain/analysis/context.ts +73 -75
  277. package/src/domain/analysis/dependencies.ts +78 -68
  278. package/src/domain/analysis/exports.ts +45 -34
  279. package/src/domain/analysis/fn-impact.ts +67 -64
  280. package/src/domain/analysis/module-map.ts +103 -8
  281. package/src/domain/analysis/query-helpers.ts +35 -0
  282. package/src/domain/graph/builder/helpers.ts +12 -6
  283. package/src/domain/graph/builder/incremental.ts +3 -2
  284. package/src/domain/graph/builder/pipeline.ts +71 -3
  285. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  286. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  287. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  288. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  289. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  290. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  291. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  292. package/src/domain/graph/resolve.ts +34 -46
  293. package/src/domain/graph/watcher.ts +12 -14
  294. package/src/domain/parser.ts +222 -97
  295. package/src/domain/search/search/cli-formatter.ts +121 -94
  296. package/src/extractors/bash.ts +97 -0
  297. package/src/extractors/c.ts +212 -0
  298. package/src/extractors/cpp.ts +298 -0
  299. package/src/extractors/csharp.ts +53 -56
  300. package/src/extractors/dart.ts +304 -0
  301. package/src/extractors/elixir.ts +251 -0
  302. package/src/extractors/go.ts +152 -134
  303. package/src/extractors/haskell.ts +235 -0
  304. package/src/extractors/hcl.ts +6 -6
  305. package/src/extractors/helpers.ts +93 -1
  306. package/src/extractors/index.ts +12 -0
  307. package/src/extractors/java.ts +43 -48
  308. package/src/extractors/javascript.ts +328 -281
  309. package/src/extractors/kotlin.ts +293 -0
  310. package/src/extractors/lua.ts +169 -0
  311. package/src/extractors/ocaml.ts +259 -0
  312. package/src/extractors/php.ts +46 -40
  313. package/src/extractors/python.ts +81 -104
  314. package/src/extractors/ruby.ts +6 -13
  315. package/src/extractors/rust.ts +65 -85
  316. package/src/extractors/scala.ts +285 -0
  317. package/src/extractors/swift.ts +293 -0
  318. package/src/extractors/zig.ts +294 -0
  319. package/src/features/ast.ts +10 -25
  320. package/src/features/audit.ts +24 -20
  321. package/src/features/branch-compare.ts +51 -4
  322. package/src/features/cfg.ts +113 -65
  323. package/src/features/check.ts +90 -74
  324. package/src/features/complexity-query.ts +181 -163
  325. package/src/features/complexity.ts +64 -1
  326. package/src/features/dataflow.ts +462 -217
  327. package/src/features/graph-enrichment.ts +161 -117
  328. package/src/features/sequence.ts +27 -4
  329. package/src/features/structure-query.ts +43 -4
  330. package/src/features/structure.ts +50 -22
  331. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  332. package/src/graph/algorithms/leiden/index.ts +67 -28
  333. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  334. package/src/graph/algorithms/leiden/partition.ts +131 -98
  335. package/src/graph/model.ts +19 -7
  336. package/src/infrastructure/config.ts +60 -58
  337. package/src/infrastructure/registry.ts +17 -14
  338. package/src/mcp/server.ts +46 -37
  339. package/src/presentation/audit.ts +72 -67
  340. package/src/presentation/branch-compare.ts +54 -50
  341. package/src/presentation/check.ts +34 -34
  342. package/src/presentation/colors.ts +2 -0
  343. package/src/presentation/complexity.ts +39 -33
  344. package/src/presentation/queries-cli/exports.ts +17 -17
  345. package/src/presentation/queries-cli/impact.ts +30 -22
  346. package/src/types.ts +195 -7
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optave/codegraph",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -131,12 +131,12 @@
131
131
  },
132
132
  "optionalDependencies": {
133
133
  "@modelcontextprotocol/sdk": "^1.0.0",
134
- "@optave/codegraph-darwin-arm64": "3.5.0",
135
- "@optave/codegraph-darwin-x64": "3.5.0",
136
- "@optave/codegraph-linux-arm64-gnu": "3.5.0",
137
- "@optave/codegraph-linux-x64-gnu": "3.5.0",
138
- "@optave/codegraph-linux-x64-musl": "3.5.0",
139
- "@optave/codegraph-win32-x64-msvc": "3.5.0"
134
+ "@optave/codegraph-darwin-arm64": "3.7.0",
135
+ "@optave/codegraph-darwin-x64": "3.7.0",
136
+ "@optave/codegraph-linux-arm64-gnu": "3.7.0",
137
+ "@optave/codegraph-linux-x64-gnu": "3.7.0",
138
+ "@optave/codegraph-linux-x64-musl": "3.7.0",
139
+ "@optave/codegraph-win32-x64-msvc": "3.7.0"
140
140
  },
141
141
  "devDependencies": {
142
142
  "@biomejs/biome": "^2.4.4",
@@ -144,19 +144,31 @@
144
144
  "@commitlint/config-conventional": "^20.0",
145
145
  "@huggingface/transformers": "^3.8.1",
146
146
  "@tree-sitter-grammars/tree-sitter-hcl": "^1.2.0",
147
+ "@tree-sitter-grammars/tree-sitter-lua": "^0.4.1",
148
+ "@tree-sitter-grammars/tree-sitter-zig": "^1.1.2",
147
149
  "@types/better-sqlite3": "^7.6.13",
148
150
  "@vitest/coverage-v8": "^4.0.18",
149
151
  "commit-and-tag-version": "^12.5",
150
152
  "husky": "^9.1",
153
+ "tree-sitter-bash": "^0.25.1",
154
+ "tree-sitter-c": "^0.24.1",
151
155
  "tree-sitter-c-sharp": "^0.23.1",
152
156
  "tree-sitter-cli": "^0.26.5",
157
+ "tree-sitter-cpp": "^0.23.4",
158
+ "tree-sitter-dart": "^1.0.0",
159
+ "tree-sitter-elixir": "^0.3.5",
153
160
  "tree-sitter-go": "^0.25.0",
161
+ "tree-sitter-haskell": "^0.23.1",
154
162
  "tree-sitter-java": "^0.23.5",
155
163
  "tree-sitter-javascript": "^0.25.0",
164
+ "tree-sitter-kotlin": "^0.3.8",
165
+ "tree-sitter-ocaml": "^0.24.2",
156
166
  "tree-sitter-php": "^0.24.2",
157
167
  "tree-sitter-python": "^0.25.0",
158
168
  "tree-sitter-ruby": "^0.23.1",
159
169
  "tree-sitter-rust": "^0.24.0",
170
+ "tree-sitter-scala": "^0.24.0",
171
+ "tree-sitter-swift": "^0.7.1",
160
172
  "tree-sitter-typescript": "^0.23.2",
161
173
  "typescript": "^6.0.2",
162
174
  "vitest": "^4.0.18"
@@ -102,11 +102,12 @@ async function ensureWasmTreesIfNeeded(
102
102
  opts: AnalysisOpts,
103
103
  rootDir: string,
104
104
  ): Promise<void> {
105
+ const doAst = opts.ast !== false;
105
106
  const doComplexity = opts.complexity !== false;
106
107
  const doCfg = opts.cfg !== false;
107
108
  const doDataflow = opts.dataflow !== false;
108
109
 
109
- if (!doComplexity && !doCfg && !doDataflow) return;
110
+ if (!doAst && !doComplexity && !doCfg && !doDataflow) return;
110
111
 
111
112
  let needsWasmTrees = false;
112
113
  for (const [relPath, symbols] of fileSymbols) {
@@ -131,6 +132,8 @@ async function ensureWasmTreesIfNeeded(
131
132
  d.endLine > d.line &&
132
133
  !d.name.includes('.');
133
134
 
135
+ // AST: need tree when native didn't provide non-call astNodes
136
+ const needsAst = doAst && !Array.isArray(symbols.astNodes) && WALK_EXTENSIONS.has(ext);
134
137
  const needsComplexity =
135
138
  doComplexity &&
136
139
  COMPLEXITY_EXTENSIONS.has(ext) &&
@@ -141,7 +144,7 @@ async function ensureWasmTreesIfNeeded(
141
144
  defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
142
145
  const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
143
146
 
144
- if (needsComplexity || needsCfg || needsDataflow) {
147
+ if (needsAst || needsComplexity || needsCfg || needsDataflow) {
145
148
  needsWasmTrees = true;
146
149
  break;
147
150
  }
@@ -159,6 +162,80 @@ async function ensureWasmTreesIfNeeded(
159
162
 
160
163
  // ─── Per-file visitor setup ─────────────────────────────────────────────
161
164
 
165
+ /** Check if a definition has a real function body (not a type signature). */
166
+ function hasFuncBody(d: {
167
+ name: string;
168
+ kind: string;
169
+ line: number;
170
+ endLine?: number | null;
171
+ }): boolean {
172
+ return (
173
+ (d.kind === 'function' || d.kind === 'method') &&
174
+ d.line > 0 &&
175
+ d.endLine != null &&
176
+ d.endLine > d.line &&
177
+ !d.name.includes('.')
178
+ );
179
+ }
180
+
181
+ /** Set up AST-store visitor if applicable. */
182
+ function setupAstVisitor(
183
+ db: BetterSqlite3Database,
184
+ relPath: string,
185
+ symbols: ExtractorOutput,
186
+ langId: string,
187
+ ext: string,
188
+ ): Visitor | null {
189
+ const astTypeMap = AST_TYPE_MAPS.get(langId);
190
+ if (!astTypeMap || !WALK_EXTENSIONS.has(ext) || Array.isArray(symbols.astNodes)) return null;
191
+ const nodeIdMap = new Map<string, number>();
192
+ for (const row of bulkNodeIdsByFile(db, relPath)) {
193
+ nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
194
+ }
195
+ return createAstStoreVisitor(astTypeMap, symbols.definitions || [], relPath, nodeIdMap);
196
+ }
197
+
198
+ /** Set up complexity visitor if any definitions need WASM complexity analysis. */
199
+ function setupComplexityVisitorForFile(
200
+ defs: Definition[],
201
+ langId: string,
202
+ walkerOpts: WalkOptions,
203
+ ): Visitor | null {
204
+ const cRules = COMPLEXITY_RULES.get(langId);
205
+ if (!cRules) return null;
206
+
207
+ const hRules = HALSTEAD_RULES.get(langId);
208
+ const needsWasmComplexity = defs.some((d) => hasFuncBody(d) && !d.complexity);
209
+ if (!needsWasmComplexity) return null;
210
+
211
+ const visitor = createComplexityVisitor(cRules, hRules, { fileLevelWalk: true, langId });
212
+
213
+ for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
214
+
215
+ const dfRules = DATAFLOW_RULES.get(langId);
216
+ walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
217
+ const nameNode = node.childForFieldName('name');
218
+ if (nameNode) return nameNode.text;
219
+ if (dfRules) return getFuncName(node, dfRules as any);
220
+ return null;
221
+ };
222
+
223
+ return visitor;
224
+ }
225
+
226
+ /** Set up CFG visitor if any definitions need WASM CFG analysis. */
227
+ function setupCfgVisitorForFile(defs: Definition[], langId: string, ext: string): Visitor | null {
228
+ const cfgRulesForLang = CFG_RULES.get(langId);
229
+ if (!cfgRulesForLang || !CFG_EXTENSIONS.has(ext)) return null;
230
+
231
+ const needsWasmCfg = defs.some(
232
+ (d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks),
233
+ );
234
+ if (!needsWasmCfg) return null;
235
+
236
+ return createCfgVisitor(cfgRulesForLang);
237
+ }
238
+
162
239
  function setupVisitors(
163
240
  db: BetterSqlite3Database,
164
241
  relPath: string,
@@ -168,10 +245,6 @@ function setupVisitors(
168
245
  ): SetupResult {
169
246
  const ext = path.extname(relPath).toLowerCase();
170
247
  const defs = symbols.definitions || [];
171
- const doAst = opts.ast !== false;
172
- const doComplexity = opts.complexity !== false;
173
- const doCfg = opts.cfg !== false;
174
- const doDataflow = opts.dataflow !== false;
175
248
 
176
249
  const visitors: Visitor[] = [];
177
250
  const walkerOpts: WalkOptions = {
@@ -180,75 +253,19 @@ function setupVisitors(
180
253
  getFunctionName: (_node: TreeSitterNode) => null,
181
254
  };
182
255
 
183
- // AST-store visitor
184
- let astVisitor: Visitor | null = null;
185
- const astTypeMap = AST_TYPE_MAPS.get(langId);
186
- if (doAst && astTypeMap && WALK_EXTENSIONS.has(ext) && !Array.isArray(symbols.astNodes)) {
187
- const nodeIdMap = new Map<string, number>();
188
- for (const row of bulkNodeIdsByFile(db, relPath)) {
189
- nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
190
- }
191
- astVisitor = createAstStoreVisitor(astTypeMap, defs, relPath, nodeIdMap);
192
- visitors.push(astVisitor);
193
- }
256
+ const astVisitor = opts.ast !== false ? setupAstVisitor(db, relPath, symbols, langId, ext) : null;
257
+ if (astVisitor) visitors.push(astVisitor);
194
258
 
195
- // Complexity visitor (file-level mode)
196
- let complexityVisitor: Visitor | null = null;
197
- const cRules = COMPLEXITY_RULES.get(langId);
198
- const hRules = HALSTEAD_RULES.get(langId);
199
- if (doComplexity && cRules) {
200
- // Only trigger WASM complexity for definitions with real function bodies.
201
- // Interface/type property signatures (dotted names, single-line span)
202
- // correctly lack native complexity data and should not trigger a fallback.
203
- const needsWasmComplexity = defs.some(
204
- (d) =>
205
- (d.kind === 'function' || d.kind === 'method') &&
206
- d.line > 0 &&
207
- d.endLine != null &&
208
- d.endLine > d.line &&
209
- !d.name.includes('.') &&
210
- !d.complexity,
211
- );
212
- if (needsWasmComplexity) {
213
- complexityVisitor = createComplexityVisitor(cRules, hRules, { fileLevelWalk: true, langId });
214
- visitors.push(complexityVisitor);
215
-
216
- for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
217
-
218
- const dfRules = DATAFLOW_RULES.get(langId);
219
- walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
220
- const nameNode = node.childForFieldName('name');
221
- if (nameNode) return nameNode.text;
222
- if (dfRules) return getFuncName(node, dfRules as any);
223
- return null;
224
- };
225
- }
226
- }
259
+ const complexityVisitor =
260
+ opts.complexity !== false ? setupComplexityVisitorForFile(defs, langId, walkerOpts) : null;
261
+ if (complexityVisitor) visitors.push(complexityVisitor);
227
262
 
228
- // CFG visitor
229
- let cfgVisitor: Visitor | null = null;
230
- const cfgRulesForLang = CFG_RULES.get(langId);
231
- if (doCfg && cfgRulesForLang && CFG_EXTENSIONS.has(ext)) {
232
- const needsWasmCfg = defs.some(
233
- (d) =>
234
- (d.kind === 'function' || d.kind === 'method') &&
235
- d.line > 0 &&
236
- d.endLine != null &&
237
- d.endLine > d.line &&
238
- !d.name.includes('.') &&
239
- d.cfg !== null &&
240
- !Array.isArray(d.cfg?.blocks),
241
- );
242
- if (needsWasmCfg) {
243
- cfgVisitor = createCfgVisitor(cfgRulesForLang);
244
- visitors.push(cfgVisitor);
245
- }
246
- }
263
+ const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId, ext) : null;
264
+ if (cfgVisitor) visitors.push(cfgVisitor);
247
265
 
248
- // Dataflow visitor
249
266
  let dataflowVisitor: Visitor | null = null;
250
267
  const dfRules = DATAFLOW_RULES.get(langId);
251
- if (doDataflow && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
268
+ if (opts.dataflow !== false && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
252
269
  dataflowVisitor = createDataflowVisitor(dfRules);
253
270
  visitors.push(dataflowVisitor);
254
271
  }
@@ -258,88 +275,80 @@ function setupVisitors(
258
275
 
259
276
  // ─── Result storage helpers ─────────────────────────────────────────────
260
277
 
261
- function storeComplexityResults(results: WalkResults, defs: Definition[], langId: string): void {
262
- const complexityResults = (results.complexity || []) as ComplexityFuncResult[];
263
- const resultByLine = new Map<number, ComplexityFuncResult[]>();
264
- for (const r of complexityResults) {
265
- if (r.funcNode) {
266
- const line = r.funcNode.startPosition.row + 1;
267
- if (!resultByLine.has(line)) resultByLine.set(line, []);
268
- resultByLine.get(line)?.push(r);
269
- }
278
+ /** Index per-function results by start line for O(1) lookup. */
279
+ function indexByLine<T extends { funcNode: TreeSitterNode }>(results: T[]): Map<number, T[]> {
280
+ const byLine = new Map<number, T[]>();
281
+ for (const r of results) {
282
+ if (!r.funcNode) continue;
283
+ const line = r.funcNode.startPosition.row + 1;
284
+ if (!byLine.has(line)) byLine.set(line, []);
285
+ byLine.get(line)?.push(r);
270
286
  }
287
+ return byLine;
288
+ }
289
+
290
+ /** Find the best matching result for a definition by line + name. */
291
+ function matchResultToDef<T extends { funcNode: TreeSitterNode }>(
292
+ candidates: T[] | undefined,
293
+ defName: string,
294
+ ): T | undefined {
295
+ if (!candidates) return undefined;
296
+ if (candidates.length === 1) return candidates[0];
297
+ return (
298
+ candidates.find((r) => {
299
+ const n = r.funcNode.childForFieldName('name');
300
+ return n && n.text === defName;
301
+ }) ?? candidates[0]
302
+ );
303
+ }
304
+
305
+ function storeComplexityResults(results: WalkResults, defs: Definition[], langId: string): void {
306
+ const byLine = indexByLine((results.complexity || []) as ComplexityFuncResult[]);
271
307
  for (const def of defs) {
272
308
  if ((def.kind === 'function' || def.kind === 'method') && def.line && !def.complexity) {
273
- const candidates = resultByLine.get(def.line);
274
- const funcResult = !candidates
275
- ? undefined
276
- : candidates.length === 1
277
- ? candidates[0]
278
- : (candidates.find((r) => {
279
- const n = r.funcNode.childForFieldName('name');
280
- return n && n.text === def.name;
281
- }) ?? candidates[0]);
282
- if (funcResult) {
283
- const { metrics } = funcResult;
284
- const loc = computeLOCMetrics(funcResult.funcNode, langId);
285
- const volume = metrics.halstead ? metrics.halstead.volume : 0;
286
- const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
287
- const mi = computeMaintainabilityIndex(volume, metrics.cyclomatic, loc.sloc, commentRatio);
288
-
289
- def.complexity = {
290
- cognitive: metrics.cognitive,
291
- cyclomatic: metrics.cyclomatic,
292
- maxNesting: metrics.maxNesting,
293
- halstead: metrics.halstead,
294
- loc,
295
- maintainabilityIndex: mi,
296
- };
297
- }
309
+ const funcResult = matchResultToDef(byLine.get(def.line), def.name);
310
+ if (!funcResult) continue;
311
+ const { metrics } = funcResult;
312
+ const loc = computeLOCMetrics(funcResult.funcNode, langId);
313
+ const volume = metrics.halstead ? metrics.halstead.volume : 0;
314
+ const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
315
+ const mi = computeMaintainabilityIndex(volume, metrics.cyclomatic, loc.sloc, commentRatio);
316
+ def.complexity = {
317
+ cognitive: metrics.cognitive,
318
+ cyclomatic: metrics.cyclomatic,
319
+ maxNesting: metrics.maxNesting,
320
+ halstead: metrics.halstead,
321
+ loc,
322
+ maintainabilityIndex: mi,
323
+ };
298
324
  }
299
325
  }
300
326
  }
301
327
 
302
328
  function storeCfgResults(results: WalkResults, defs: Definition[]): void {
303
- const cfgResults = (results.cfg || []) as CfgFuncResult[];
304
- const cfgByLine = new Map<number, CfgFuncResult[]>();
305
- for (const r of cfgResults) {
306
- if (r.funcNode) {
307
- const line = r.funcNode.startPosition.row + 1;
308
- if (!cfgByLine.has(line)) cfgByLine.set(line, []);
309
- cfgByLine.get(line)?.push(r);
310
- }
311
- }
329
+ const byLine = indexByLine((results.cfg || []) as CfgFuncResult[]);
312
330
  for (const def of defs) {
313
331
  if (
314
332
  (def.kind === 'function' || def.kind === 'method') &&
315
333
  def.line &&
316
334
  !def.cfg?.blocks?.length
317
335
  ) {
318
- const candidates = cfgByLine.get(def.line);
319
- const cfgResult = !candidates
320
- ? undefined
321
- : candidates.length === 1
322
- ? candidates[0]
323
- : (candidates.find((r) => {
324
- const n = r.funcNode.childForFieldName('name');
325
- return n && n.text === def.name;
326
- }) ?? candidates[0]);
327
- if (cfgResult) {
328
- def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
329
-
330
- // Override complexity's cyclomatic with CFG-derived value (single source of truth)
331
- if (def.complexity && cfgResult.cyclomatic != null) {
332
- def.complexity.cyclomatic = cfgResult.cyclomatic;
333
- const { loc, halstead } = def.complexity;
334
- const volume = halstead ? halstead.volume : 0;
335
- const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
336
- def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
337
- volume,
338
- cfgResult.cyclomatic,
339
- loc?.sloc ?? 0,
340
- commentRatio,
341
- );
342
- }
336
+ const cfgResult = matchResultToDef(byLine.get(def.line), def.name);
337
+ if (!cfgResult) continue;
338
+ def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
339
+
340
+ // Override complexity's cyclomatic with CFG-derived value (single source of truth)
341
+ if (def.complexity && cfgResult.cyclomatic != null) {
342
+ def.complexity.cyclomatic = cfgResult.cyclomatic;
343
+ const { loc, halstead } = def.complexity;
344
+ const volume = halstead ? halstead.volume : 0;
345
+ const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
346
+ def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
347
+ volume,
348
+ cfgResult.cyclomatic,
349
+ loc?.sloc ?? 0,
350
+ commentRatio,
351
+ );
343
352
  }
344
353
  }
345
354
  }
@@ -14,7 +14,7 @@ interface AstStoreRow {
14
14
  kind: string;
15
15
  name: string | null | undefined;
16
16
  text: string | null;
17
- receiver: null;
17
+ receiver: string | null;
18
18
  parentNodeId: number | null;
19
19
  }
20
20
 
@@ -52,6 +52,14 @@ function extractCallName(node: TreeSitterNode): string {
52
52
  return node.text?.split('(')[0] || '?';
53
53
  }
54
54
 
55
+ /** Extract receiver for call expressions (e.g. "obj" in "obj.method()"). */
56
+ function extractCallReceiver(node: TreeSitterNode): string | null {
57
+ const fn = node.childForFieldName('function');
58
+ if (!fn || fn.type !== 'member_expression') return null;
59
+ const obj = fn.childForFieldName('object');
60
+ return obj ? obj.text : null;
61
+ }
62
+
55
63
  function extractName(kind: string, node: TreeSitterNode): string | null {
56
64
  if (kind === 'throw') {
57
65
  for (let i = 0; i < node.childCount; i++) {
@@ -161,10 +169,12 @@ export function createAstStoreVisitor(
161
169
  const line = node.startPosition.row + 1;
162
170
  let name: string | null | undefined;
163
171
  let text: string | null = null;
172
+ let receiver: string | null = null;
164
173
 
165
174
  if (kind === 'call') {
166
175
  name = extractCallName(node);
167
176
  text = truncate(node.text);
177
+ receiver = extractCallReceiver(node);
168
178
  } else if (kind === 'new') {
169
179
  name = extractNewName(node);
170
180
  text = truncate(node.text);
@@ -190,7 +200,7 @@ export function createAstStoreVisitor(
190
200
  kind,
191
201
  name,
192
202
  text,
193
- receiver: null,
203
+ receiver,
194
204
  parentNodeId: resolveParentNodeId(line),
195
205
  });
196
206
 
@@ -201,6 +211,9 @@ export function createAstStoreVisitor(
201
211
  name: 'ast-store',
202
212
 
203
213
  enterNode(node: TreeSitterNode, _context: VisitorContext): EnterNodeResult | undefined {
214
+ // Guard: skip re-collection but do NOT skipChildren — node.id (memory address)
215
+ // can be reused by tree-sitter, so a collision would incorrectly suppress an
216
+ // unrelated subtree. The parent call's skipChildren handles the intended case.
204
217
  if (matched.has(node.id)) return;
205
218
 
206
219
  const kind = astTypeMap[node.type];
@@ -87,6 +87,16 @@ function classifyBranchNode(
87
87
  }
88
88
  }
89
89
 
90
+ function classifyLogicalOp(node: TreeSitterNode, cRules: AnyRules, acc: ComplexityAcc): void {
91
+ const op = node.child(1)?.type;
92
+ if (!op || !cRules.logicalOperators.has(op)) return;
93
+ acc.cyclomatic++;
94
+ const parent = node.parent;
95
+ const sameSequence =
96
+ parent != null && parent.type === cRules.logicalNodeType && parent.child(1)?.type === op;
97
+ if (!sameSequence) acc.cognitive++;
98
+ }
99
+
90
100
  function classifyPlainElse(
91
101
  node: TreeSitterNode,
92
102
  type: string,
@@ -215,17 +225,7 @@ export function createComplexityVisitor(
215
225
  if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
216
226
 
217
227
  if (type === cRules.logicalNodeType) {
218
- const op = node.child(1)?.type;
219
- if (op && cRules.logicalOperators.has(op)) {
220
- acc.cyclomatic++;
221
- const parent = node.parent;
222
- let sameSequence = false;
223
- if (parent && parent.type === cRules.logicalNodeType) {
224
- const parentOp = parent.child(1)?.type;
225
- if (parentOp === op) sameSequence = true;
226
- }
227
- if (!sameSequence) acc.cognitive++;
228
- }
228
+ classifyLogicalOp(node, cRules, acc);
229
229
  }
230
230
 
231
231
  if (type === cRules.optionalChainType) acc.cyclomatic++;