@optave/codegraph 3.7.0 → 3.8.1

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 (401) hide show
  1. package/README.md +28 -16
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +195 -30
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  10. package/dist/ast-analysis/rules/javascript.js +0 -1
  11. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  12. package/dist/ast-analysis/shared.d.ts.map +1 -1
  13. package/dist/ast-analysis/shared.js +24 -19
  14. package/dist/ast-analysis/shared.js.map +1 -1
  15. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor-utils.js +55 -39
  17. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  18. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitor.js +91 -70
  20. package/dist/ast-analysis/visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/ast-store-visitor.js +52 -129
  23. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  26. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  27. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  28. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  29. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  30. package/dist/cli/commands/ast.js +2 -2
  31. package/dist/cli/commands/ast.js.map +1 -1
  32. package/dist/cli/commands/watch.d.ts.map +1 -1
  33. package/dist/cli/commands/watch.js +16 -2
  34. package/dist/cli/commands/watch.js.map +1 -1
  35. package/dist/db/connection.d.ts.map +1 -1
  36. package/dist/db/connection.js +29 -26
  37. package/dist/db/connection.js.map +1 -1
  38. package/dist/db/query-builder.d.ts.map +1 -1
  39. package/dist/db/query-builder.js +16 -5
  40. package/dist/db/query-builder.js.map +1 -1
  41. package/dist/db/repository/base.d.ts +10 -0
  42. package/dist/db/repository/base.d.ts.map +1 -1
  43. package/dist/db/repository/base.js +17 -0
  44. package/dist/db/repository/base.js.map +1 -1
  45. package/dist/db/repository/native-repository.d.ts +6 -1
  46. package/dist/db/repository/native-repository.d.ts.map +1 -1
  47. package/dist/db/repository/native-repository.js +77 -1
  48. package/dist/db/repository/native-repository.js.map +1 -1
  49. package/dist/db/repository/nodes.d.ts.map +1 -1
  50. package/dist/db/repository/nodes.js +8 -4
  51. package/dist/db/repository/nodes.js.map +1 -1
  52. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  53. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  54. package/dist/db/repository/sqlite-repository.js +26 -0
  55. package/dist/db/repository/sqlite-repository.js.map +1 -1
  56. package/dist/domain/analysis/brief.d.ts.map +1 -1
  57. package/dist/domain/analysis/brief.js +13 -17
  58. package/dist/domain/analysis/brief.js.map +1 -1
  59. package/dist/domain/analysis/context.d.ts.map +1 -1
  60. package/dist/domain/analysis/context.js +14 -11
  61. package/dist/domain/analysis/context.js.map +1 -1
  62. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  63. package/dist/domain/analysis/dependencies.js +53 -52
  64. package/dist/domain/analysis/dependencies.js.map +1 -1
  65. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  66. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  67. package/dist/domain/analysis/fn-impact.js +33 -31
  68. package/dist/domain/analysis/fn-impact.js.map +1 -1
  69. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  70. package/dist/domain/analysis/implementations.js +11 -19
  71. package/dist/domain/analysis/implementations.js.map +1 -1
  72. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  73. package/dist/domain/analysis/module-map.js +55 -76
  74. package/dist/domain/analysis/module-map.js.map +1 -1
  75. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  76. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  77. package/dist/domain/analysis/query-helpers.js +15 -1
  78. package/dist/domain/analysis/query-helpers.js.map +1 -1
  79. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  80. package/dist/domain/graph/builder/pipeline.js +315 -43
  81. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  83. package/dist/domain/graph/builder/stages/build-edges.js +106 -1
  84. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  85. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  86. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  87. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  88. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.js +99 -51
  90. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  91. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  92. package/dist/domain/graph/builder/stages/finalize.js +34 -7
  93. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  94. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  95. package/dist/domain/graph/builder/stages/insert-nodes.js +50 -26
  96. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  97. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  98. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  99. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  100. package/dist/domain/graph/cycles.d.ts +6 -0
  101. package/dist/domain/graph/cycles.d.ts.map +1 -1
  102. package/dist/domain/graph/cycles.js +114 -22
  103. package/dist/domain/graph/cycles.js.map +1 -1
  104. package/dist/domain/graph/resolve.js +1 -1
  105. package/dist/domain/graph/resolve.js.map +1 -1
  106. package/dist/domain/graph/watcher.d.ts +2 -0
  107. package/dist/domain/graph/watcher.d.ts.map +1 -1
  108. package/dist/domain/graph/watcher.js +170 -75
  109. package/dist/domain/graph/watcher.js.map +1 -1
  110. package/dist/domain/parser.d.ts +1 -6
  111. package/dist/domain/parser.d.ts.map +1 -1
  112. package/dist/domain/parser.js +101 -32
  113. package/dist/domain/parser.js.map +1 -1
  114. package/dist/domain/search/generator.js +1 -1
  115. package/dist/domain/search/generator.js.map +1 -1
  116. package/dist/domain/search/models.d.ts +4 -3
  117. package/dist/domain/search/models.d.ts.map +1 -1
  118. package/dist/domain/search/models.js +18 -5
  119. package/dist/domain/search/models.js.map +1 -1
  120. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  121. package/dist/domain/search/search/hybrid.js +29 -18
  122. package/dist/domain/search/search/hybrid.js.map +1 -1
  123. package/dist/extractors/clojure.d.ts +12 -0
  124. package/dist/extractors/clojure.d.ts.map +1 -0
  125. package/dist/extractors/clojure.js +245 -0
  126. package/dist/extractors/clojure.js.map +1 -0
  127. package/dist/extractors/cuda.d.ts +11 -0
  128. package/dist/extractors/cuda.d.ts.map +1 -0
  129. package/dist/extractors/cuda.js +302 -0
  130. package/dist/extractors/cuda.js.map +1 -0
  131. package/dist/extractors/erlang.d.ts +14 -0
  132. package/dist/extractors/erlang.d.ts.map +1 -0
  133. package/dist/extractors/erlang.js +239 -0
  134. package/dist/extractors/erlang.js.map +1 -0
  135. package/dist/extractors/fsharp.d.ts +13 -0
  136. package/dist/extractors/fsharp.d.ts.map +1 -0
  137. package/dist/extractors/fsharp.js +218 -0
  138. package/dist/extractors/fsharp.js.map +1 -0
  139. package/dist/extractors/gleam.d.ts +14 -0
  140. package/dist/extractors/gleam.d.ts.map +1 -0
  141. package/dist/extractors/gleam.js +229 -0
  142. package/dist/extractors/gleam.js.map +1 -0
  143. package/dist/extractors/go.js +36 -33
  144. package/dist/extractors/go.js.map +1 -1
  145. package/dist/extractors/groovy.d.ts +10 -0
  146. package/dist/extractors/groovy.d.ts.map +1 -0
  147. package/dist/extractors/groovy.js +304 -0
  148. package/dist/extractors/groovy.js.map +1 -0
  149. package/dist/extractors/helpers.d.ts.map +1 -1
  150. package/dist/extractors/helpers.js +40 -29
  151. package/dist/extractors/helpers.js.map +1 -1
  152. package/dist/extractors/index.d.ts +11 -0
  153. package/dist/extractors/index.d.ts.map +1 -1
  154. package/dist/extractors/index.js +11 -0
  155. package/dist/extractors/index.js.map +1 -1
  156. package/dist/extractors/java.js +58 -46
  157. package/dist/extractors/java.js.map +1 -1
  158. package/dist/extractors/javascript.js +46 -45
  159. package/dist/extractors/javascript.js.map +1 -1
  160. package/dist/extractors/julia.d.ts +16 -0
  161. package/dist/extractors/julia.d.ts.map +1 -0
  162. package/dist/extractors/julia.js +287 -0
  163. package/dist/extractors/julia.js.map +1 -0
  164. package/dist/extractors/kotlin.js +84 -78
  165. package/dist/extractors/kotlin.js.map +1 -1
  166. package/dist/extractors/objc.d.ts +9 -0
  167. package/dist/extractors/objc.d.ts.map +1 -0
  168. package/dist/extractors/objc.js +406 -0
  169. package/dist/extractors/objc.js.map +1 -0
  170. package/dist/extractors/ocaml.js +74 -0
  171. package/dist/extractors/ocaml.js.map +1 -1
  172. package/dist/extractors/python.js +29 -24
  173. package/dist/extractors/python.js.map +1 -1
  174. package/dist/extractors/r.d.ts +13 -0
  175. package/dist/extractors/r.d.ts.map +1 -0
  176. package/dist/extractors/r.js +251 -0
  177. package/dist/extractors/r.js.map +1 -0
  178. package/dist/extractors/rust.js +41 -32
  179. package/dist/extractors/rust.js.map +1 -1
  180. package/dist/extractors/solidity.d.ts +9 -0
  181. package/dist/extractors/solidity.d.ts.map +1 -0
  182. package/dist/extractors/solidity.js +365 -0
  183. package/dist/extractors/solidity.js.map +1 -0
  184. package/dist/extractors/swift.js +83 -81
  185. package/dist/extractors/swift.js.map +1 -1
  186. package/dist/extractors/verilog.d.ts +9 -0
  187. package/dist/extractors/verilog.d.ts.map +1 -0
  188. package/dist/extractors/verilog.js +286 -0
  189. package/dist/extractors/verilog.js.map +1 -0
  190. package/dist/extractors/zig.js +58 -60
  191. package/dist/extractors/zig.js.map +1 -1
  192. package/dist/features/ast.d.ts +16 -14
  193. package/dist/features/ast.d.ts.map +1 -1
  194. package/dist/features/ast.js +84 -83
  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 +8 -6
  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 +69 -72
  201. package/dist/features/branch-compare.js.map +1 -1
  202. package/dist/features/communities.d.ts.map +1 -1
  203. package/dist/features/communities.js +19 -7
  204. package/dist/features/communities.js.map +1 -1
  205. package/dist/features/complexity.d.ts.map +1 -1
  206. package/dist/features/complexity.js +120 -125
  207. package/dist/features/complexity.js.map +1 -1
  208. package/dist/features/dataflow.d.ts.map +1 -1
  209. package/dist/features/dataflow.js +136 -137
  210. package/dist/features/dataflow.js.map +1 -1
  211. package/dist/features/flow.d.ts.map +1 -1
  212. package/dist/features/flow.js +84 -79
  213. package/dist/features/flow.js.map +1 -1
  214. package/dist/features/structure-query.d.ts.map +1 -1
  215. package/dist/features/structure-query.js +69 -65
  216. package/dist/features/structure-query.js.map +1 -1
  217. package/dist/graph/algorithms/bfs.d.ts +2 -0
  218. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  219. package/dist/graph/algorithms/bfs.js +27 -0
  220. package/dist/graph/algorithms/bfs.js.map +1 -1
  221. package/dist/graph/algorithms/centrality.d.ts +2 -0
  222. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  223. package/dist/graph/algorithms/centrality.js +28 -0
  224. package/dist/graph/algorithms/centrality.js.map +1 -1
  225. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  226. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  227. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  228. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  229. package/dist/graph/algorithms/leiden/partition.js +288 -266
  230. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  231. package/dist/graph/algorithms/louvain.d.ts +3 -4
  232. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  233. package/dist/graph/algorithms/louvain.js +29 -0
  234. package/dist/graph/algorithms/louvain.js.map +1 -1
  235. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  236. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  237. package/dist/graph/algorithms/shortest-path.js +18 -1
  238. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  239. package/dist/graph/model.d.ts.map +1 -1
  240. package/dist/graph/model.js +5 -1
  241. package/dist/graph/model.js.map +1 -1
  242. package/dist/infrastructure/config.d.ts.map +1 -1
  243. package/dist/infrastructure/config.js +6 -4
  244. package/dist/infrastructure/config.js.map +1 -1
  245. package/dist/infrastructure/suppress.d.ts +25 -0
  246. package/dist/infrastructure/suppress.d.ts.map +1 -0
  247. package/dist/infrastructure/suppress.js +43 -0
  248. package/dist/infrastructure/suppress.js.map +1 -0
  249. package/dist/mcp/server.d.ts.map +1 -1
  250. package/dist/mcp/server.js +29 -24
  251. package/dist/mcp/server.js.map +1 -1
  252. package/dist/presentation/dataflow.d.ts.map +1 -1
  253. package/dist/presentation/dataflow.js +47 -38
  254. package/dist/presentation/dataflow.js.map +1 -1
  255. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  256. package/dist/presentation/diff-impact-mermaid.js +60 -51
  257. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  258. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  259. package/dist/presentation/queries-cli/exports.js +20 -14
  260. package/dist/presentation/queries-cli/exports.js.map +1 -1
  261. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  262. package/dist/presentation/queries-cli/impact.js +15 -13
  263. package/dist/presentation/queries-cli/impact.js.map +1 -1
  264. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  265. package/dist/presentation/queries-cli/inspect.js +101 -79
  266. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  267. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  268. package/dist/presentation/queries-cli/overview.js +25 -16
  269. package/dist/presentation/queries-cli/overview.js.map +1 -1
  270. package/dist/presentation/queries-cli/path.js +26 -20
  271. package/dist/presentation/queries-cli/path.js.map +1 -1
  272. package/dist/presentation/result-formatter.d.ts +10 -0
  273. package/dist/presentation/result-formatter.d.ts.map +1 -1
  274. package/dist/presentation/result-formatter.js +16 -1
  275. package/dist/presentation/result-formatter.js.map +1 -1
  276. package/dist/presentation/viewer.d.ts.map +1 -1
  277. package/dist/presentation/viewer.js +18 -12
  278. package/dist/presentation/viewer.js.map +1 -1
  279. package/dist/shared/errors.d.ts +5 -0
  280. package/dist/shared/errors.d.ts.map +1 -1
  281. package/dist/shared/errors.js +5 -0
  282. package/dist/shared/errors.js.map +1 -1
  283. package/dist/shared/hierarchy.d.ts +8 -2
  284. package/dist/shared/hierarchy.d.ts.map +1 -1
  285. package/dist/shared/hierarchy.js +42 -1
  286. package/dist/shared/hierarchy.js.map +1 -1
  287. package/dist/shared/normalize.d.ts +6 -1
  288. package/dist/shared/normalize.d.ts.map +1 -1
  289. package/dist/shared/normalize.js +20 -12
  290. package/dist/shared/normalize.js.map +1 -1
  291. package/dist/shared/paginate.d.ts +0 -9
  292. package/dist/shared/paginate.d.ts.map +1 -1
  293. package/dist/shared/paginate.js +0 -15
  294. package/dist/shared/paginate.js.map +1 -1
  295. package/dist/types.d.ts +129 -3
  296. package/dist/types.d.ts.map +1 -1
  297. package/grammars/tree-sitter-clojure.wasm +0 -0
  298. package/grammars/tree-sitter-cuda.wasm +0 -0
  299. package/grammars/tree-sitter-erlang.wasm +0 -0
  300. package/grammars/tree-sitter-fsharp.wasm +0 -0
  301. package/grammars/tree-sitter-gleam.wasm +0 -0
  302. package/grammars/tree-sitter-groovy.wasm +0 -0
  303. package/grammars/tree-sitter-julia.wasm +0 -0
  304. package/grammars/tree-sitter-objc.wasm +0 -0
  305. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  306. package/grammars/tree-sitter-r.wasm +0 -0
  307. package/grammars/tree-sitter-solidity.wasm +0 -0
  308. package/grammars/tree-sitter-verilog.wasm +0 -0
  309. package/package.json +18 -7
  310. package/src/ast-analysis/engine.ts +245 -42
  311. package/src/ast-analysis/metrics.ts +33 -11
  312. package/src/ast-analysis/rules/javascript.ts +0 -1
  313. package/src/ast-analysis/shared.ts +33 -24
  314. package/src/ast-analysis/visitor-utils.ts +52 -32
  315. package/src/ast-analysis/visitor.ts +132 -71
  316. package/src/ast-analysis/visitors/ast-store-visitor.ts +49 -119
  317. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  318. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  319. package/src/cli/commands/ast.ts +2 -2
  320. package/src/cli/commands/watch.ts +16 -2
  321. package/src/db/connection.ts +29 -28
  322. package/src/db/query-builder.ts +15 -3
  323. package/src/db/repository/base.ts +20 -0
  324. package/src/db/repository/native-repository.ts +79 -1
  325. package/src/db/repository/nodes.ts +13 -8
  326. package/src/db/repository/sqlite-repository.ts +29 -0
  327. package/src/domain/analysis/brief.ts +15 -25
  328. package/src/domain/analysis/context.ts +17 -10
  329. package/src/domain/analysis/dependencies.ts +67 -76
  330. package/src/domain/analysis/fn-impact.ts +36 -43
  331. package/src/domain/analysis/implementations.ts +11 -17
  332. package/src/domain/analysis/module-map.ts +58 -92
  333. package/src/domain/analysis/query-helpers.ts +18 -1
  334. package/src/domain/graph/builder/pipeline.ts +366 -41
  335. package/src/domain/graph/builder/stages/build-edges.ts +162 -1
  336. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  337. package/src/domain/graph/builder/stages/detect-changes.ts +110 -56
  338. package/src/domain/graph/builder/stages/finalize.ts +41 -11
  339. package/src/domain/graph/builder/stages/insert-nodes.ts +75 -39
  340. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  341. package/src/domain/graph/cycles.ts +110 -23
  342. package/src/domain/graph/resolve.ts +1 -1
  343. package/src/domain/graph/watcher.ts +202 -96
  344. package/src/domain/parser.ts +122 -28
  345. package/src/domain/search/generator.ts +1 -1
  346. package/src/domain/search/models.ts +17 -4
  347. package/src/domain/search/search/hybrid.ts +69 -51
  348. package/src/extractors/clojure.ts +273 -0
  349. package/src/extractors/cuda.ts +316 -0
  350. package/src/extractors/erlang.ts +252 -0
  351. package/src/extractors/fsharp.ts +253 -0
  352. package/src/extractors/gleam.ts +246 -0
  353. package/src/extractors/go.ts +43 -33
  354. package/src/extractors/groovy.ts +332 -0
  355. package/src/extractors/helpers.ts +37 -23
  356. package/src/extractors/index.ts +11 -0
  357. package/src/extractors/java.ts +66 -47
  358. package/src/extractors/javascript.ts +45 -46
  359. package/src/extractors/julia.ts +318 -0
  360. package/src/extractors/kotlin.ts +84 -77
  361. package/src/extractors/objc.ts +431 -0
  362. package/src/extractors/ocaml.ts +78 -0
  363. package/src/extractors/python.ts +31 -25
  364. package/src/extractors/r.ts +253 -0
  365. package/src/extractors/rust.ts +37 -29
  366. package/src/extractors/solidity.ts +394 -0
  367. package/src/extractors/swift.ts +81 -80
  368. package/src/extractors/verilog.ts +315 -0
  369. package/src/extractors/zig.ts +58 -61
  370. package/src/features/ast.ts +131 -112
  371. package/src/features/audit.ts +8 -6
  372. package/src/features/branch-compare.ts +105 -79
  373. package/src/features/communities.ts +25 -10
  374. package/src/features/complexity.ts +171 -134
  375. package/src/features/dataflow.ts +165 -175
  376. package/src/features/flow.ts +129 -92
  377. package/src/features/structure-query.ts +79 -64
  378. package/src/graph/algorithms/bfs.ts +34 -0
  379. package/src/graph/algorithms/centrality.ts +30 -0
  380. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  381. package/src/graph/algorithms/leiden/partition.ts +359 -294
  382. package/src/graph/algorithms/louvain.ts +31 -4
  383. package/src/graph/algorithms/shortest-path.ts +20 -1
  384. package/src/graph/model.ts +6 -1
  385. package/src/infrastructure/config.ts +6 -4
  386. package/src/infrastructure/suppress.ts +47 -0
  387. package/src/mcp/server.ts +53 -37
  388. package/src/presentation/dataflow.ts +50 -44
  389. package/src/presentation/diff-impact-mermaid.ts +104 -62
  390. package/src/presentation/queries-cli/exports.ts +21 -13
  391. package/src/presentation/queries-cli/impact.ts +15 -13
  392. package/src/presentation/queries-cli/inspect.ts +100 -81
  393. package/src/presentation/queries-cli/overview.ts +26 -16
  394. package/src/presentation/queries-cli/path.ts +33 -25
  395. package/src/presentation/result-formatter.ts +19 -1
  396. package/src/presentation/viewer.ts +42 -14
  397. package/src/shared/errors.ts +6 -0
  398. package/src/shared/hierarchy.ts +50 -2
  399. package/src/shared/normalize.ts +31 -12
  400. package/src/shared/paginate.ts +0 -17
  401. package/src/types.ts +138 -3
@@ -15,10 +15,13 @@
15
15
  * output). This eliminates redundant tree traversals per file.
16
16
  */
17
17
 
18
+ import fs from 'node:fs';
18
19
  import path from 'node:path';
19
20
  import { performance } from 'node:perf_hooks';
20
21
  import { bulkNodeIdsByFile } from '../db/index.js';
21
22
  import { debug } from '../infrastructure/logger.js';
23
+ import { loadNative } from '../infrastructure/native.js';
24
+ import { toErrorMessage } from '../shared/errors.js';
22
25
  import type {
23
26
  AnalysisOpts,
24
27
  AnalysisTiming,
@@ -30,6 +33,9 @@ import type {
30
33
  Definition,
31
34
  EngineOpts,
32
35
  ExtractorOutput,
36
+ NativeAddon,
37
+ NativeFunctionCfgResult,
38
+ NativeFunctionComplexityResult,
33
39
  TreeSitterNode,
34
40
  Visitor,
35
41
  WalkOptions,
@@ -95,6 +101,214 @@ async function getParserModule(): Promise<typeof import('../domain/parser.js')>
95
101
  return _parserModule;
96
102
  }
97
103
 
104
+ // ─── Native standalone analysis ─────────────────────────────────────────
105
+
106
+ interface NativeAnalysisNeeds {
107
+ complexity: boolean;
108
+ cfg: boolean;
109
+ dataflow: boolean;
110
+ }
111
+
112
+ /**
113
+ * Try native Rust analysis for files missing complexity/CFG/dataflow data.
114
+ * Reads source from disk, calls the native standalone functions, and stores
115
+ * results directly on definitions/symbols.
116
+ */
117
+
118
+ /** Determine which native analyses a file still needs. */
119
+ function detectNativeNeeds(
120
+ symbols: ExtractorOutput,
121
+ ext: string,
122
+ langId: string,
123
+ opts: { doComplexity: boolean; doCfg: boolean; doDataflow: boolean },
124
+ ): NativeAnalysisNeeds {
125
+ const defs = symbols.definitions || [];
126
+ const langSupportsComplexity = COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(langId);
127
+ const langSupportsCfg = CFG_EXTENSIONS.has(ext) || CFG_RULES.has(langId);
128
+ const langSupportsDataflow = DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(langId);
129
+
130
+ return {
131
+ complexity:
132
+ opts.doComplexity &&
133
+ langSupportsComplexity &&
134
+ defs.some((d) => hasFuncBody(d) && !d.complexity),
135
+ cfg:
136
+ opts.doCfg &&
137
+ langSupportsCfg &&
138
+ defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks)),
139
+ dataflow: opts.doDataflow && !symbols.dataflow && langSupportsDataflow,
140
+ };
141
+ }
142
+
143
+ /** Run native analysis passes for a single file. */
144
+ function runNativeFileAnalysis(
145
+ native: NativeAddon,
146
+ source: string,
147
+ absPath: string,
148
+ relPath: string,
149
+ langId: string,
150
+ symbols: ExtractorOutput,
151
+ needs: NativeAnalysisNeeds,
152
+ ): void {
153
+ const defs = symbols.definitions || [];
154
+
155
+ if (needs.complexity && native.analyzeComplexity) {
156
+ try {
157
+ const results = native.analyzeComplexity(source, absPath, langId);
158
+ storeNativeComplexityResults(results, defs);
159
+ } catch (err: unknown) {
160
+ debug(`native analyzeComplexity failed for ${relPath}: ${toErrorMessage(err)}`);
161
+ }
162
+ }
163
+
164
+ if (needs.cfg && native.buildCfgAnalysis) {
165
+ try {
166
+ const results = native.buildCfgAnalysis(source, absPath, langId);
167
+ storeNativeCfgResults(results, defs);
168
+ } catch (err: unknown) {
169
+ debug(`native buildCfgAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
170
+ }
171
+ }
172
+
173
+ if (needs.dataflow && native.extractDataflowAnalysis) {
174
+ try {
175
+ const result = native.extractDataflowAnalysis(source, absPath, langId);
176
+ if (result) symbols.dataflow = result;
177
+ } catch (err: unknown) {
178
+ debug(`native extractDataflowAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
179
+ }
180
+ }
181
+ }
182
+
183
+ function runNativeAnalysis(
184
+ native: NativeAddon,
185
+ fileSymbols: Map<string, ExtractorOutput>,
186
+ rootDir: string,
187
+ opts: AnalysisOpts,
188
+ extToLang: Map<string, string>,
189
+ ): void {
190
+ const optsFlags = {
191
+ doComplexity: opts.complexity !== false,
192
+ doCfg: opts.cfg !== false,
193
+ doDataflow: opts.dataflow !== false,
194
+ };
195
+
196
+ for (const [relPath, symbols] of fileSymbols) {
197
+ if (symbols._tree) continue;
198
+ const ext = path.extname(relPath).toLowerCase();
199
+ const langId = symbols._langId || extToLang.get(ext);
200
+ if (!langId) continue;
201
+
202
+ const needs = detectNativeNeeds(symbols, ext, langId, optsFlags);
203
+ if (!needs.complexity && !needs.cfg && !needs.dataflow) continue;
204
+
205
+ const absPath = path.join(rootDir, relPath);
206
+ let source: string;
207
+ try {
208
+ source = fs.readFileSync(absPath, 'utf-8');
209
+ } catch (e) {
210
+ debug(`runNativeAnalysis: failed to read ${relPath}: ${toErrorMessage(e)}`);
211
+ continue;
212
+ }
213
+
214
+ runNativeFileAnalysis(native, source, absPath, relPath, langId, symbols, needs);
215
+ }
216
+ }
217
+
218
+ /** Store native complexity results on definitions, matched by line number. */
219
+ function storeNativeComplexityResults(
220
+ results: NativeFunctionComplexityResult[],
221
+ defs: Definition[],
222
+ ): void {
223
+ const byLine = new Map<number, NativeFunctionComplexityResult[]>();
224
+ for (const r of results) {
225
+ if (!byLine.has(r.line)) byLine.set(r.line, []);
226
+ byLine.get(r.line)!.push(r);
227
+ }
228
+
229
+ for (const def of defs) {
230
+ if ((def.kind === 'function' || def.kind === 'method') && def.line && !def.complexity) {
231
+ const candidates = byLine.get(def.line);
232
+ if (!candidates) continue;
233
+ const match =
234
+ candidates.length === 1
235
+ ? candidates[0]
236
+ : (candidates.find((r) => r.name === def.name) ?? candidates[0]);
237
+ if (!match) continue;
238
+ const { complexity: c } = match;
239
+ def.complexity = {
240
+ cognitive: c.cognitive,
241
+ cyclomatic: c.cyclomatic,
242
+ maxNesting: c.maxNesting,
243
+ halstead: c.halstead
244
+ ? {
245
+ volume: c.halstead.volume,
246
+ difficulty: c.halstead.difficulty,
247
+ effort: c.halstead.effort,
248
+ bugs: c.halstead.bugs,
249
+ }
250
+ : undefined,
251
+ loc: c.loc
252
+ ? { loc: c.loc.loc, sloc: c.loc.sloc, commentLines: c.loc.commentLines }
253
+ : undefined,
254
+ maintainabilityIndex: c.maintainabilityIndex ?? undefined,
255
+ };
256
+ }
257
+ }
258
+ }
259
+
260
+ /** Override a definition's cyclomatic complexity with a CFG-derived value and recompute MI. */
261
+ function overrideCyclomaticFromCfg(def: Definition, cfgCyclomatic: number): void {
262
+ if (!def.complexity) return;
263
+ if (cfgCyclomatic <= 0) {
264
+ debug(`overrideCyclomaticFromCfg: skipping ${def.name} — cfgCyclomatic=${cfgCyclomatic}`);
265
+ return;
266
+ }
267
+ def.complexity.cyclomatic = cfgCyclomatic;
268
+ const { loc, halstead } = def.complexity;
269
+ const volume = halstead ? halstead.volume : 0;
270
+ const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
271
+ def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
272
+ volume,
273
+ cfgCyclomatic,
274
+ loc?.sloc ?? 0,
275
+ commentRatio,
276
+ );
277
+ }
278
+
279
+ /** Store native CFG results on definitions, matched by line number. */
280
+ function storeNativeCfgResults(results: NativeFunctionCfgResult[], defs: Definition[]): void {
281
+ const byLine = new Map<number, NativeFunctionCfgResult[]>();
282
+ for (const r of results) {
283
+ if (!byLine.has(r.line)) byLine.set(r.line, []);
284
+ byLine.get(r.line)!.push(r);
285
+ }
286
+
287
+ for (const def of defs) {
288
+ if (
289
+ (def.kind === 'function' || def.kind === 'method') &&
290
+ def.line &&
291
+ def.cfg !== null &&
292
+ !def.cfg?.blocks?.length
293
+ ) {
294
+ const candidates = byLine.get(def.line);
295
+ if (!candidates) continue;
296
+ const match =
297
+ candidates.length === 1
298
+ ? candidates[0]
299
+ : (candidates.find((r) => r.name === def.name) ?? candidates[0]);
300
+ if (!match) continue;
301
+ def.cfg = match.cfg;
302
+
303
+ // Override complexity cyclomatic with CFG-derived value
304
+ const { edges, blocks } = match.cfg;
305
+ if (edges && blocks) {
306
+ overrideCyclomaticFromCfg(def, edges.length - blocks.length + 2);
307
+ }
308
+ }
309
+ }
310
+ }
311
+
98
312
  // ─── WASM pre-parse ─────────────────────────────────────────────────────
99
313
 
100
314
  async function ensureWasmTreesIfNeeded(
@@ -115,34 +329,22 @@ async function ensureWasmTreesIfNeeded(
115
329
  const ext = path.extname(relPath).toLowerCase();
116
330
  const defs = symbols.definitions || [];
117
331
 
118
- // Only consider definitions with a real function body.
119
- // Interface/type property signatures are extracted as methods but correctly
120
- // lack complexity/CFG data from the native engine. Exclude them by:
121
- // 1. Single-line span (endLine === line) — type property on one line
122
- // 2. Dotted names (e.g. "Interface.prop") — child definitions of types
123
- const hasFuncBody = (d: {
124
- name: string;
125
- kind: string;
126
- line: number;
127
- endLine?: number | null;
128
- }) =>
129
- (d.kind === 'function' || d.kind === 'method') &&
130
- d.line > 0 &&
131
- d.endLine != null &&
132
- d.endLine > d.line &&
133
- !d.name.includes('.');
134
-
135
332
  // AST: need tree when native didn't provide non-call astNodes
136
- const needsAst = doAst && !Array.isArray(symbols.astNodes) && WALK_EXTENSIONS.has(ext);
333
+ const lid = symbols._langId || '';
334
+ const needsAst =
335
+ doAst &&
336
+ !Array.isArray(symbols.astNodes) &&
337
+ (WALK_EXTENSIONS.has(ext) || AST_TYPE_MAPS.has(lid));
137
338
  const needsComplexity =
138
339
  doComplexity &&
139
- COMPLEXITY_EXTENSIONS.has(ext) &&
340
+ (COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(lid)) &&
140
341
  defs.some((d) => hasFuncBody(d) && !d.complexity);
141
342
  const needsCfg =
142
343
  doCfg &&
143
- CFG_EXTENSIONS.has(ext) &&
344
+ (CFG_EXTENSIONS.has(ext) || CFG_RULES.has(lid)) &&
144
345
  defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
145
- const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
346
+ const needsDataflow =
347
+ doDataflow && !symbols.dataflow && (DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(lid));
146
348
 
147
349
  if (needsAst || needsComplexity || needsCfg || needsDataflow) {
148
350
  needsWasmTrees = true;
@@ -155,7 +357,7 @@ async function ensureWasmTreesIfNeeded(
155
357
  const { ensureWasmTrees } = await getParserModule();
156
358
  await ensureWasmTrees(fileSymbols, rootDir);
157
359
  } catch (err: unknown) {
158
- debug(`ensureWasmTrees failed: ${(err as Error).message}`);
360
+ debug(`ensureWasmTrees failed: ${toErrorMessage(err)}`);
159
361
  }
160
362
  }
161
363
  }
@@ -224,9 +426,9 @@ function setupComplexityVisitorForFile(
224
426
  }
225
427
 
226
428
  /** Set up CFG visitor if any definitions need WASM CFG analysis. */
227
- function setupCfgVisitorForFile(defs: Definition[], langId: string, ext: string): Visitor | null {
429
+ function setupCfgVisitorForFile(defs: Definition[], langId: string): Visitor | null {
228
430
  const cfgRulesForLang = CFG_RULES.get(langId);
229
- if (!cfgRulesForLang || !CFG_EXTENSIONS.has(ext)) return null;
431
+ if (!cfgRulesForLang) return null;
230
432
 
231
433
  const needsWasmCfg = defs.some(
232
434
  (d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks),
@@ -260,12 +462,12 @@ function setupVisitors(
260
462
  opts.complexity !== false ? setupComplexityVisitorForFile(defs, langId, walkerOpts) : null;
261
463
  if (complexityVisitor) visitors.push(complexityVisitor);
262
464
 
263
- const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId, ext) : null;
465
+ const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId) : null;
264
466
  if (cfgVisitor) visitors.push(cfgVisitor);
265
467
 
266
468
  let dataflowVisitor: Visitor | null = null;
267
469
  const dfRules = DATAFLOW_RULES.get(langId);
268
- if (opts.dataflow !== false && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
470
+ if (opts.dataflow !== false && dfRules && !symbols.dataflow) {
269
471
  dataflowVisitor = createDataflowVisitor(dfRules);
270
472
  visitors.push(dataflowVisitor);
271
473
  }
@@ -338,17 +540,8 @@ function storeCfgResults(results: WalkResults, defs: Definition[]): void {
338
540
  def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
339
541
 
340
542
  // 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
- );
543
+ if (cfgResult.cyclomatic != null) {
544
+ overrideCyclomaticFromCfg(def, cfgResult.cyclomatic);
352
545
  }
353
546
  }
354
547
  }
@@ -370,7 +563,7 @@ async function delegateToBuildFunctions(
370
563
  const { buildAstNodes } = await import('../features/ast.js');
371
564
  await buildAstNodes(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
372
565
  } catch (err: unknown) {
373
- debug(`buildAstNodes failed: ${(err as Error).message}`);
566
+ debug(`buildAstNodes failed: ${toErrorMessage(err)}`);
374
567
  }
375
568
  timing.astMs = performance.now() - t0;
376
569
  }
@@ -381,7 +574,7 @@ async function delegateToBuildFunctions(
381
574
  const { buildComplexityMetrics } = await import('../features/complexity.js');
382
575
  await buildComplexityMetrics(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
383
576
  } catch (err: unknown) {
384
- debug(`buildComplexityMetrics failed: ${(err as Error).message}`);
577
+ debug(`buildComplexityMetrics failed: ${toErrorMessage(err)}`);
385
578
  }
386
579
  timing.complexityMs = performance.now() - t0;
387
580
  }
@@ -392,7 +585,7 @@ async function delegateToBuildFunctions(
392
585
  const { buildCFGData } = await import('../features/cfg.js');
393
586
  await buildCFGData(db, fileSymbols, rootDir, engineOpts);
394
587
  } catch (err: unknown) {
395
- debug(`buildCFGData failed: ${(err as Error).message}`);
588
+ debug(`buildCFGData failed: ${toErrorMessage(err)}`);
396
589
  }
397
590
  timing.cfgMs = performance.now() - t0;
398
591
  }
@@ -403,7 +596,7 @@ async function delegateToBuildFunctions(
403
596
  const { buildDataflowEdges } = await import('../features/dataflow.js');
404
597
  await buildDataflowEdges(db, fileSymbols, rootDir, engineOpts);
405
598
  } catch (err: unknown) {
406
- debug(`buildDataflowEdges failed: ${(err as Error).message}`);
599
+ debug(`buildDataflowEdges failed: ${toErrorMessage(err)}`);
407
600
  }
408
601
  timing.dataflowMs = performance.now() - t0;
409
602
  }
@@ -429,7 +622,17 @@ export async function runAnalyses(
429
622
 
430
623
  const extToLang = buildExtToLangMap();
431
624
 
432
- // WASM pre-parse for files that need it
625
+ // Native analysis pass: try Rust standalone functions before WASM fallback.
626
+ // This fills in complexity/CFG/dataflow for files that the native parse pipeline
627
+ // missed, avoiding the need to parse with WASM + run JS visitors.
628
+ const native = loadNative();
629
+ if (native?.analyzeComplexity ?? native?.buildCfgAnalysis ?? native?.extractDataflowAnalysis) {
630
+ const t0native = performance.now();
631
+ runNativeAnalysis(native, fileSymbols, rootDir, opts, extToLang);
632
+ debug(`native standalone analysis: ${(performance.now() - t0native).toFixed(1)}ms`);
633
+ }
634
+
635
+ // WASM pre-parse for files that still need it (AST store, or native gaps)
433
636
  await ensureWasmTreesIfNeeded(fileSymbols, opts, rootDir);
434
637
 
435
638
  // Unified pre-walk: run all applicable visitors in a single DFS per file
@@ -9,6 +9,16 @@ import type { HalsteadDerivedMetrics, LOCMetrics, TreeSitterNode } from '../type
9
9
 
10
10
  // ─── Halstead Derived Metrics ─────────────────────────────────────────────
11
11
 
12
+ /** Halstead delivered-bugs denominator (industry standard: V / 3000). */
13
+ const HALSTEAD_BUGS_DIVISOR = 3000;
14
+
15
+ /** Sum all values in a count map. */
16
+ function sumCounts(map: Map<string, number>): number {
17
+ let total = 0;
18
+ for (const c of map.values()) total += c;
19
+ return total;
20
+ }
21
+
12
22
  /**
13
23
  * Compute Halstead derived metrics from raw operator/operand counts.
14
24
  *
@@ -22,17 +32,15 @@ export function computeHalsteadDerived(
22
32
  ): HalsteadDerivedMetrics {
23
33
  const n1 = operators.size;
24
34
  const n2 = operands.size;
25
- let bigN1 = 0;
26
- for (const c of operators.values()) bigN1 += c;
27
- let bigN2 = 0;
28
- for (const c of operands.values()) bigN2 += c;
35
+ const bigN1 = sumCounts(operators);
36
+ const bigN2 = sumCounts(operands);
29
37
 
30
38
  const vocabulary = n1 + n2;
31
39
  const length = bigN1 + bigN2;
32
40
  const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
33
41
  const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
34
42
  const effort = difficulty * volume;
35
- const bugs = volume / 3000;
43
+ const bugs = volume / HALSTEAD_BUGS_DIVISOR;
36
44
 
37
45
  return {
38
46
  n1,
@@ -97,10 +105,20 @@ export function computeLOCMetrics(functionNode: TreeSitterNode, language?: strin
97
105
  // ─── Maintainability Index ────────────────────────────────────────────────
98
106
 
99
107
  /**
100
- * Compute normalized Maintainability Index (0-100 scale).
101
- *
102
- * Original SEI formula: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
108
+ * SEI Maintainability Index formula coefficients.
109
+ * Original: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
103
110
  * Microsoft normalization: max(0, min(100, MI * 100/171))
111
+ */
112
+ const MI_BASE = 171;
113
+ const MI_VOLUME_COEFF = 5.2;
114
+ const MI_CYCLOMATIC_COEFF = 0.23;
115
+ const MI_LOC_COEFF = 16.2;
116
+ const MI_COMMENT_AMPLITUDE = 50;
117
+ const MI_COMMENT_SCALE = 2.4;
118
+ const MI_NORMALIZE_SCALE = 100;
119
+
120
+ /**
121
+ * Compute normalized Maintainability Index (0-100 scale).
104
122
  *
105
123
  * @param {number} volume - Halstead volume
106
124
  * @param {number} cyclomatic - Cyclomatic complexity
@@ -117,12 +135,16 @@ export function computeMaintainabilityIndex(
117
135
  const safeVolume = Math.max(volume, 1);
118
136
  const safeSLOC = Math.max(sloc, 1);
119
137
 
120
- let mi = 171 - 5.2 * Math.log(safeVolume) - 0.23 * cyclomatic - 16.2 * Math.log(safeSLOC);
138
+ let mi =
139
+ MI_BASE -
140
+ MI_VOLUME_COEFF * Math.log(safeVolume) -
141
+ MI_CYCLOMATIC_COEFF * cyclomatic -
142
+ MI_LOC_COEFF * Math.log(safeSLOC);
121
143
 
122
144
  if (commentRatio != null && commentRatio > 0) {
123
- mi += 50 * Math.sin(Math.sqrt(2.4 * commentRatio));
145
+ mi += MI_COMMENT_AMPLITUDE * Math.sin(Math.sqrt(MI_COMMENT_SCALE * commentRatio));
124
146
  }
125
147
 
126
- const normalized = Math.max(0, Math.min(100, (mi * 100) / 171));
148
+ const normalized = Math.max(0, Math.min(MI_NORMALIZE_SCALE, (mi * MI_NORMALIZE_SCALE) / MI_BASE));
127
149
  return +normalized.toFixed(1);
128
150
  }
@@ -237,7 +237,6 @@ export const dataflow: DataflowRulesConfig = makeDataflowRules({
237
237
  // ─── AST Node Types ───────────────────────────────────────────────────────
238
238
 
239
239
  export const astTypes: Record<string, string> | null = {
240
- call_expression: 'call',
241
240
  new_expression: 'new',
242
241
  throw_statement: 'throw',
243
242
  await_expression: 'await',
@@ -150,6 +150,38 @@ export function makeDataflowRules(overrides: Partial<DataflowRulesConfig>): Data
150
150
 
151
151
  // ─── AST Helpers ──────────────────────────────────────────────────────────
152
152
 
153
+ /** Compute the span (row count) of a tree-sitter node. */
154
+ function nodeSpan(node: TreeSitterNode): number {
155
+ return node.endPosition.row - node.startPosition.row;
156
+ }
157
+
158
+ /**
159
+ * Recursively search for the narrowest function node at the target line.
160
+ */
161
+ function searchFunctionNode(
162
+ node: TreeSitterNode,
163
+ targetStart: number,
164
+ functionNodeTypes: Set<string>,
165
+ best: TreeSitterNode | null,
166
+ ): TreeSitterNode | null {
167
+ const nodeStart = node.startPosition.row;
168
+ const nodeEnd = node.endPosition.row;
169
+
170
+ // Prune branches outside range
171
+ if (nodeEnd < targetStart || nodeStart > targetStart + 1) return best;
172
+
173
+ if (functionNodeTypes.has(node.type) && nodeStart === targetStart) {
174
+ if (!best || nodeSpan(node) < nodeSpan(best)) {
175
+ best = node;
176
+ }
177
+ }
178
+
179
+ for (let i = 0; i < node.childCount; i++) {
180
+ best = searchFunctionNode(node.child(i)!, targetStart, functionNodeTypes, best);
181
+ }
182
+ return best;
183
+ }
184
+
153
185
  export function findFunctionNode(
154
186
  rootNode: TreeSitterNode,
155
187
  startLine: number,
@@ -158,30 +190,7 @@ export function findFunctionNode(
158
190
  ): TreeSitterNode | null {
159
191
  // tree-sitter lines are 0-indexed
160
192
  const targetStart = startLine - 1;
161
-
162
- let best: TreeSitterNode | null = null;
163
-
164
- function search(node: TreeSitterNode): void {
165
- const nodeStart = node.startPosition.row;
166
- const nodeEnd = node.endPosition.row;
167
-
168
- // Prune branches outside range
169
- if (nodeEnd < targetStart || nodeStart > targetStart + 1) return;
170
-
171
- if (rules.functionNodes.has(node.type) && nodeStart === targetStart) {
172
- // Found a function node at the right position — pick it
173
- if (!best || nodeEnd - nodeStart < best.endPosition.row - best.startPosition.row) {
174
- best = node;
175
- }
176
- }
177
-
178
- for (let i = 0; i < node.childCount; i++) {
179
- search(node.child(i)!);
180
- }
181
- }
182
-
183
- search(rootNode);
184
- return best;
193
+ return searchFunctionNode(rootNode, targetStart, rules.functionNodes, null);
185
194
  }
186
195
 
187
196
  // ─── Extension / Language Mapping ─────────────────────────────────────────
@@ -88,6 +88,41 @@ export function extractParams(
88
88
  return result;
89
89
  }
90
90
 
91
+ /** Extract names from a rest parameter (e.g. `...args`). */
92
+ function extractRestParamNames(node: TreeSitterNode, rules: LanguageRules): string[] {
93
+ const nameNode = node.childForFieldName('name');
94
+ if (nameNode) return [nameNode.text];
95
+ for (const child of node.namedChildren) {
96
+ if (child.type === rules.paramIdentifier) return [child.text];
97
+ }
98
+ return [];
99
+ }
100
+
101
+ /** Extract names from an object destructuring pattern (e.g. `{ a, b: c }`). */
102
+ function extractObjectDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
103
+ const names: string[] = [];
104
+ for (const child of node.namedChildren) {
105
+ if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
106
+ names.push(child.text);
107
+ } else if (rules.pairPatternType && child.type === rules.pairPatternType) {
108
+ const value = child.childForFieldName('value');
109
+ if (value) names.push(...extractParamNames(value, rules));
110
+ } else if (rules.restParamType && child.type === rules.restParamType) {
111
+ names.push(...extractParamNames(child, rules));
112
+ }
113
+ }
114
+ return names;
115
+ }
116
+
117
+ /** Extract names from an array destructuring pattern (e.g. `[a, b]`). */
118
+ function extractArrayDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
119
+ const names: string[] = [];
120
+ for (const child of node.namedChildren) {
121
+ names.push(...extractParamNames(child, rules));
122
+ }
123
+ return names;
124
+ }
125
+
91
126
  /**
92
127
  * Extract parameter names from a single parameter node.
93
128
  */
@@ -113,35 +148,15 @@ export function extractParamNames(node: TreeSitterNode | null, rules: LanguageRu
113
148
  }
114
149
 
115
150
  if (rules.restParamType && t === rules.restParamType) {
116
- const nameNode = node.childForFieldName('name');
117
- if (nameNode) return [nameNode.text];
118
- for (const child of node.namedChildren) {
119
- if (child.type === rules.paramIdentifier) return [child.text];
120
- }
121
- return [];
151
+ return extractRestParamNames(node, rules);
122
152
  }
123
153
 
124
154
  if (rules.objectDestructType && t === rules.objectDestructType) {
125
- const names: string[] = [];
126
- for (const child of node.namedChildren) {
127
- if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
128
- names.push(child.text);
129
- } else if (rules.pairPatternType && child.type === rules.pairPatternType) {
130
- const value = child.childForFieldName('value');
131
- if (value) names.push(...extractParamNames(value, rules));
132
- } else if (rules.restParamType && child.type === rules.restParamType) {
133
- names.push(...extractParamNames(child, rules));
134
- }
135
- }
136
- return names;
155
+ return extractObjectDestructNames(node, rules);
137
156
  }
138
157
 
139
158
  if (rules.arrayDestructType && t === rules.arrayDestructType) {
140
- const names: string[] = [];
141
- for (const child of node.namedChildren) {
142
- names.push(...extractParamNames(child, rules));
143
- }
144
- return names;
159
+ return extractArrayDestructNames(node, rules);
145
160
  }
146
161
 
147
162
  return [];
@@ -155,6 +170,19 @@ export function isIdent(nodeType: string, rules: LanguageRules): boolean {
155
170
  return rules.extraIdentifierTypes ? rules.extraIdentifierTypes.has(nodeType) : false;
156
171
  }
157
172
 
173
+ /** Resolve callee name from an optional chain node (e.g. `obj?.method()`). */
174
+ function resolveOptionalChainCallee(fn: TreeSitterNode, rules: LanguageRules): string | null {
175
+ const target = fn.namedChildren[0];
176
+ if (!target) return null;
177
+ if (target.type === rules.memberNode) {
178
+ const prop = target.childForFieldName(rules.memberPropertyField);
179
+ return prop ? prop.text : null;
180
+ }
181
+ if (target.type === 'identifier') return target.text;
182
+ const prop = fn.childForFieldName(rules.memberPropertyField);
183
+ return prop ? prop.text : null;
184
+ }
185
+
158
186
  /**
159
187
  * Resolve the name a call expression is calling using rules.
160
188
  */
@@ -170,15 +198,7 @@ export function resolveCalleeName(callNode: TreeSitterNode, rules: LanguageRules
170
198
  return prop ? prop.text : null;
171
199
  }
172
200
  if (rules.optionalChainNode && fn.type === rules.optionalChainNode) {
173
- const target = fn.namedChildren[0];
174
- if (!target) return null;
175
- if (target.type === rules.memberNode) {
176
- const prop = target.childForFieldName(rules.memberPropertyField);
177
- return prop ? prop.text : null;
178
- }
179
- if (target.type === 'identifier') return target.text;
180
- const prop = fn.childForFieldName(rules.memberPropertyField);
181
- return prop ? prop.text : null;
201
+ return resolveOptionalChainCallee(fn, rules);
182
202
  }
183
203
  return null;
184
204
  }