gitnexus 1.6.8-rc.8 → 1.6.8

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 (439) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +23 -2
  135. package/dist/core/ingestion/languages/c-cpp.js +17 -5
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +8 -1
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +44 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  238. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  239. package/dist/core/ingestion/workers/parse-worker.d.ts +23 -0
  240. package/dist/core/ingestion/workers/parse-worker.js +81 -53
  241. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  242. package/dist/core/ingestion/workers/post-result.js +87 -0
  243. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  244. package/dist/core/ingestion/workers/result-merge.js +43 -0
  245. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -40
  246. package/dist/core/ingestion/workers/worker-pool.js +43 -16
  247. package/dist/core/lbug/csv-generator.d.ts +51 -3
  248. package/dist/core/lbug/csv-generator.js +369 -255
  249. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  250. package/dist/core/lbug/lbug-adapter.js +346 -84
  251. package/dist/core/lbug/lbug-config.d.ts +19 -0
  252. package/dist/core/lbug/lbug-config.js +29 -0
  253. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  254. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  255. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  256. package/dist/core/lbug/pool-adapter.js +90 -1
  257. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  258. package/dist/core/lbug/rel-pair-routing.js +140 -0
  259. package/dist/core/lbug/schema.d.ts +2 -2
  260. package/dist/core/lbug/schema.js +3 -0
  261. package/dist/core/logger.d.ts +0 -5
  262. package/dist/core/run-analyze.d.ts +96 -0
  263. package/dist/core/run-analyze.js +207 -3
  264. package/dist/core/tree-sitter/parser-loader.js +5 -4
  265. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  266. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  267. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  268. package/dist/core/wiki/generator.js +3 -1
  269. package/dist/core/wiki/graph-queries.d.ts +6 -0
  270. package/dist/core/wiki/graph-queries.js +9 -1
  271. package/dist/mcp/http-transport.d.ts +134 -0
  272. package/dist/mcp/http-transport.js +503 -0
  273. package/dist/mcp/local/local-backend.d.ts +110 -24
  274. package/dist/mcp/local/local-backend.js +1225 -19
  275. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  276. package/dist/mcp/local/pdg-impact.js +1972 -0
  277. package/dist/mcp/resources.js +13 -1
  278. package/dist/mcp/tools.d.ts +12 -0
  279. package/dist/mcp/tools.js +214 -7
  280. package/dist/server/analyze-launch.d.ts +29 -0
  281. package/dist/server/analyze-launch.js +137 -0
  282. package/dist/server/analyze-upload.d.ts +33 -0
  283. package/dist/server/analyze-upload.js +123 -0
  284. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  285. package/dist/server/analyze-worker-ipc.js +16 -0
  286. package/dist/server/analyze-worker.d.ts +17 -1
  287. package/dist/server/analyze-worker.js +7 -1
  288. package/dist/server/api.d.ts +17 -0
  289. package/dist/server/api.js +132 -168
  290. package/dist/server/git-clone.d.ts +43 -2
  291. package/dist/server/git-clone.js +211 -16
  292. package/dist/server/mcp-http.d.ts +5 -6
  293. package/dist/server/mcp-http.js +9 -82
  294. package/dist/server/middleware.d.ts +40 -0
  295. package/dist/server/middleware.js +92 -0
  296. package/dist/server/private-ip.d.ts +1 -0
  297. package/dist/server/private-ip.js +15 -0
  298. package/dist/server/upload-ingest.d.ts +56 -0
  299. package/dist/server/upload-ingest.js +276 -0
  300. package/dist/server/upload-paths.d.ts +37 -0
  301. package/dist/server/upload-paths.js +57 -0
  302. package/dist/server/upload-sweep.d.ts +20 -0
  303. package/dist/server/upload-sweep.js +57 -0
  304. package/dist/storage/git.js +5 -8
  305. package/dist/storage/parse-cache.d.ts +22 -1
  306. package/dist/storage/parse-cache.js +44 -10
  307. package/dist/storage/repo-manager.d.ts +121 -10
  308. package/dist/storage/repo-manager.js +43 -5
  309. package/dist/types/pipeline.d.ts +9 -0
  310. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  311. package/hooks/claude/gitnexus-hook.cjs +110 -13
  312. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  313. package/package.json +8 -2
  314. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  315. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  316. package/scripts/cross-platform-tests.ts +1 -0
  317. package/skills/gitnexus-debugging.md +16 -4
  318. package/skills/gitnexus-exploring.md +3 -3
  319. package/skills/gitnexus-guide.md +33 -0
  320. package/skills/gitnexus-pdg-query.md +89 -0
  321. package/skills/gitnexus-refactoring.md +1 -1
  322. package/skills/gitnexus-taint-analysis.md +178 -0
  323. package/vendor/tree-sitter-c/package.json +1 -1
  324. package/vendor/tree-sitter-dart/package.json +1 -1
  325. package/vendor/tree-sitter-kotlin/package.json +1 -1
  326. package/vendor/tree-sitter-proto/package.json +1 -1
  327. package/vendor/tree-sitter-swift/package.json +1 -1
  328. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  329. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  330. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  331. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  332. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  333. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  334. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  335. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  336. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  337. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  338. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  339. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  340. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  341. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  342. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  343. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  344. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  345. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  346. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  347. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  348. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  349. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  350. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  351. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  352. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  353. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  354. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  355. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  356. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  357. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  358. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  359. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  360. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  361. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  362. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  363. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  364. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  365. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  366. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  367. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  368. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  369. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  370. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  371. package/web/assets/index-BvYev1UJ.js +635 -0
  372. package/web/assets/index-_lgn7hs5.css +2 -0
  373. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  374. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  375. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  376. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  377. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  378. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  379. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  380. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  381. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  382. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  383. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  384. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  385. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  386. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  387. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  388. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  389. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  390. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  391. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  392. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  393. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  394. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  395. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  396. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  397. package/web/index.html +13 -15
  398. package/scripts/materialize-vendor-grammars.cjs +0 -97
  399. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  400. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  401. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  402. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  403. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  404. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  405. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  406. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  407. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  408. package/web/assets/chunk-CilyBKbf.js +0 -1
  409. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  410. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  411. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  412. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  413. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  414. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  415. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  416. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  417. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  418. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  419. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  420. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  421. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  422. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  423. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  424. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  425. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  426. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  427. package/web/assets/index-BKWA-m7o.css +0 -2
  428. package/web/assets/index-CG6q8eTs.js +0 -626
  429. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  430. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  431. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  432. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  433. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  434. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  435. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  436. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  437. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  438. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  439. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,756 @@
1
+ import { CfgBuilder } from '../cfg-builder.js';
2
+ import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
3
+ import { RubyHarvester } from './ruby-harvest.js';
4
+ /** Ruby node types that own a CFG-bearing function/closure body. */
5
+ const RUBY_FUNCTION_TYPES = new Set(['method', 'singleton_method', 'do_block', 'block', 'lambda']);
6
+ /** Statement node types that break a basic block (everything else coalesces). */
7
+ const CONTROL_FLOW_TYPES = new Set([
8
+ 'if',
9
+ 'unless',
10
+ 'if_modifier',
11
+ 'unless_modifier',
12
+ 'while',
13
+ 'until',
14
+ 'while_modifier',
15
+ 'until_modifier',
16
+ 'for',
17
+ 'case',
18
+ 'case_match',
19
+ 'begin',
20
+ 'return',
21
+ 'break',
22
+ 'next',
23
+ 'redo',
24
+ 'retry',
25
+ 'body_statement',
26
+ 'block_body',
27
+ ]);
28
+ /**
29
+ * Value-position branch constructs (#2205). When one of these is an `assignment`
30
+ * RHS (`x = if … / case …`) or a `return` value, it is modeled as control flow
31
+ * (its arms carry control dependence) instead of coalescing inline. `if`/`unless`
32
+ * always branch (the condition gates the then-arm even without `else`).
33
+ */
34
+ const VALUE_BRANCH_EXPR_TYPES = new Set(['if', 'unless', 'case', 'case_match']);
35
+ const startLineOf = (n) => n.startPosition.row + 1;
36
+ const endLineOf = (n) => n.endPosition.row + 1;
37
+ /**
38
+ * Per-function Ruby walk state. One instance per function so the
39
+ * {@link ControlFlowContext}, the exception-handler stack, and the `begin`
40
+ * re-enter stack (for `retry`) are scoped to that function and never leak.
41
+ */
42
+ class RubyCfgWalk {
43
+ builder;
44
+ harvest;
45
+ cfc = new ControlFlowContext();
46
+ /** Stack of exception-handler entry blocks (rescue / ensure) a raise jumps to. */
47
+ handlers = [];
48
+ /** Stack of nearest-enclosing `begin` protected-body entry blocks (for `retry`). */
49
+ beginEntries = [];
50
+ /**
51
+ * For a block / lambda function (`do … end`, `{ … }`, `->(){}`), the body entry
52
+ * a `redo` re-enters when there is no enclosing real loop. A bare block is
53
+ * implicitly re-runnable by `redo`; set once the body's first block is known.
54
+ */
55
+ blockRedoTarget;
56
+ /** Record the block-function body entry that a bare `redo` re-enters. */
57
+ setBlockRedoTarget(entry) {
58
+ this.blockRedoTarget = entry;
59
+ }
60
+ constructor(builder, harvest) {
61
+ this.builder = builder;
62
+ this.harvest = harvest;
63
+ }
64
+ /** Statements of a body node (`then`/`do`/`else`/`ensure`/`body_statement`/…), no comments. */
65
+ statementsOf(body) {
66
+ return body.namedChildren.filter((c) => c.type !== 'comment');
67
+ }
68
+ /** Visit a body that may be a wrapper (`then`/`do`/`block_body`/`body_statement`) or a single statement. */
69
+ visitBody(node) {
70
+ return this.builder.withNesting(() => {
71
+ if (!node)
72
+ return null;
73
+ if (this.isBodyWrapper(node))
74
+ return this.visitSeq(this.statementsOf(node));
75
+ return this.visitStmt(node);
76
+ });
77
+ }
78
+ isBodyWrapper(node) {
79
+ return (node.type === 'then' ||
80
+ node.type === 'do' ||
81
+ node.type === 'else' ||
82
+ node.type === 'ensure' ||
83
+ node.type === 'body_statement' ||
84
+ node.type === 'block_body');
85
+ }
86
+ /** Wire a sequence of statements, coalescing straight-line runs into blocks. */
87
+ visitSeq(stmts) {
88
+ return this.builder.withNesting(() => {
89
+ let entry;
90
+ let dangling = [];
91
+ let openSimple;
92
+ for (const stmt of stmts) {
93
+ // A value-position branch on an assignment RHS (`x = if …`) is NOT in
94
+ // CONTROL_FLOW_TYPES (its node is `assignment`), so route it out of the
95
+ // coalescing path explicitly so visitStmt models its arms (#2205).
96
+ if (CONTROL_FLOW_TYPES.has(stmt.type) || this.isValueBranchAssignment(stmt)) {
97
+ openSimple = undefined; // close any open straight-line block
98
+ const res = this.visitStmt(stmt);
99
+ if (res === null)
100
+ continue; // transparent (empty nested block)
101
+ if (entry === undefined)
102
+ entry = res.entry;
103
+ else
104
+ this.builder.connect(dangling, res.entry, 'seq');
105
+ dangling = [...res.exits];
106
+ }
107
+ else {
108
+ if (openSimple === undefined) {
109
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
110
+ if (entry === undefined)
111
+ entry = idx;
112
+ else
113
+ this.builder.connect(dangling, idx, 'seq');
114
+ openSimple = idx;
115
+ dangling = [idx];
116
+ }
117
+ else {
118
+ this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
119
+ }
120
+ }
121
+ }
122
+ if (entry === undefined)
123
+ return null;
124
+ return { entry, exits: dangling };
125
+ });
126
+ }
127
+ /** Dispatch one statement to its handler. Non-null except for empty blocks. */
128
+ visitStmt(stmt) {
129
+ switch (stmt.type) {
130
+ case 'if':
131
+ case 'unless':
132
+ return this.visitIf(stmt);
133
+ case 'if_modifier':
134
+ case 'unless_modifier':
135
+ return this.visitIfModifier(stmt);
136
+ case 'while':
137
+ case 'until':
138
+ return this.visitWhile(stmt);
139
+ case 'while_modifier':
140
+ case 'until_modifier':
141
+ return this.visitWhileModifier(stmt);
142
+ case 'for':
143
+ return this.visitFor(stmt);
144
+ case 'case':
145
+ return this.visitCase(stmt);
146
+ case 'case_match':
147
+ return this.visitCaseMatch(stmt);
148
+ case 'begin':
149
+ return this.visitBegin(stmt);
150
+ case 'return':
151
+ return this.visitReturn(stmt);
152
+ case 'break':
153
+ return this.visitBreak(stmt);
154
+ case 'next':
155
+ return this.visitNext(stmt);
156
+ case 'redo':
157
+ return this.visitRedo(stmt);
158
+ case 'retry':
159
+ return this.visitRetry(stmt);
160
+ case 'body_statement':
161
+ return this.visitImplicitBegin(stmt);
162
+ case 'block_body':
163
+ return this.visitSeq(this.statementsOf(stmt));
164
+ case 'assignment': {
165
+ // `x = if c then a else b end` / `x = case … end` (#2205): model the RHS
166
+ // branch as control flow and bind the LHS on the rejoin.
167
+ const branch = this.assignmentBranch(stmt);
168
+ if (branch)
169
+ return this.visitBindBranch(stmt, branch);
170
+ return this.visitSimple(stmt);
171
+ }
172
+ default:
173
+ return this.visitSimple(stmt);
174
+ }
175
+ }
176
+ visitSimple(stmt) {
177
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
178
+ return { entry: idx, exits: [idx] };
179
+ }
180
+ /**
181
+ * `return [expr]` — threads through EVERY active `ensure` before EXIT.
182
+ * (A value-position branch RETURN is handled the idiomatic Ruby way already:
183
+ * `def f; if c then a else b end; end` is the implicit last-expression and is
184
+ * modeled as statement position. Bare `return if … end` is not a clean
185
+ * grammar shape — tree-sitter-ruby drops the value — so it stays a plain
186
+ * return; the assignment carrier is the value-branch fix here, #2205.)
187
+ */
188
+ visitReturn(stmt) {
189
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
190
+ wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
191
+ return { entry: idx, exits: [] };
192
+ }
193
+ /** `break [expr]` — exits the nearest loop/block; threads through any `ensure`. */
194
+ visitBreak(stmt) {
195
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
196
+ const res = this.cfc.resolveBreak();
197
+ const { target, finalizers } = res ?? {
198
+ target: this.builder.exitIndex,
199
+ finalizers: this.cfc.finalizersForReturn(),
200
+ };
201
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
202
+ return { entry: idx, exits: [] };
203
+ }
204
+ /** `next [expr]` ≈ continue — re-tests the nearest loop header; threads `ensure`. */
205
+ visitNext(stmt) {
206
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
207
+ const res = this.cfc.resolveContinue();
208
+ const { target, finalizers } = res ?? {
209
+ target: this.builder.exitIndex,
210
+ finalizers: this.cfc.finalizersForReturn(),
211
+ };
212
+ wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
213
+ return { entry: idx, exits: [] };
214
+ }
215
+ /**
216
+ * `redo` — re-run the current loop/block body WITHOUT re-evaluating the
217
+ * condition. Modeled as a `loop-back` to the nearest loop's continue target
218
+ * (the loop header in the visitor's model). Outside a loop it routes to EXIT.
219
+ */
220
+ visitRedo(stmt) {
221
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
222
+ // A `redo` inside a real loop re-runs that loop's body (its continue target);
223
+ // a `redo` inside a bare block re-enters the block body. Falls back to EXIT
224
+ // when neither exists (single-exit preserved).
225
+ const target = this.cfc.continueTarget() ?? this.blockRedoTarget;
226
+ if (target !== undefined)
227
+ this.builder.edge(idx, target, 'loop-back');
228
+ else
229
+ this.builder.edge(idx, this.builder.exitIndex, 'seq');
230
+ return { entry: idx, exits: [] };
231
+ }
232
+ /**
233
+ * `retry` — re-enter the nearest enclosing `begin`'s protected body. Modeled as
234
+ * a `loop-back` to that begin's entry block. Outside any begin it routes to
235
+ * EXIT (single-exit preserved) — a documented approximation.
236
+ */
237
+ visitRetry(stmt) {
238
+ const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
239
+ const begin = this.beginEntries[this.beginEntries.length - 1];
240
+ if (begin !== undefined)
241
+ this.builder.edge(idx, begin, 'loop-back');
242
+ else
243
+ this.builder.edge(idx, this.builder.exitIndex, 'seq');
244
+ return { entry: idx, exits: [] };
245
+ }
246
+ /**
247
+ * `if cond … elsif cond … else …` and `unless cond … else …`. Ruby has no
248
+ * nested-if else chain: an `if`/`unless` carries `condition`/`consequence`(then)
249
+ * plus an optional `alternative` that is an `elsif` (its own
250
+ * condition/consequence/alternative) or a trailing `else`. `unless` inverts the
251
+ * sense — its consequence is the cond-FALSE arm.
252
+ */
253
+ /** The value-position branch on an `assignment` RHS (`x = if … / case …`), or
254
+ * undefined when the RHS is anything else (#2205). */
255
+ assignmentBranch(stmt) {
256
+ if (stmt.type !== 'assignment')
257
+ return undefined;
258
+ const right = stmt.childForFieldName('right');
259
+ return right && VALUE_BRANCH_EXPR_TYPES.has(right.type) ? right : undefined;
260
+ }
261
+ /** Whether a statement is an `assignment` with a value-position-branch RHS —
262
+ * routes it out of `visitSeq`'s coalescing path so its arms are modeled. */
263
+ isValueBranchAssignment(stmt) {
264
+ return this.assignmentBranch(stmt) !== undefined;
265
+ }
266
+ /** Model a value-position `if`/`unless`/`case`/`case_match` as control flow,
267
+ * bypassing the statement/value-position distinction (#2205). */
268
+ visitBranchExpr(node) {
269
+ switch (node.type) {
270
+ case 'if':
271
+ case 'unless':
272
+ return this.visitIf(node);
273
+ case 'case':
274
+ return this.visitCase(node);
275
+ case 'case_match':
276
+ return this.visitCaseMatch(node);
277
+ default:
278
+ return this.visitSimple(node); // unreachable — guarded by VALUE_BRANCH_EXPR_TYPES
279
+ }
280
+ }
281
+ /**
282
+ * `x = if … / case …` (#2205): visit the RHS branch as control flow, then rejoin
283
+ * its arms at a facts-only continuation carrying ONLY the LHS target def (the
284
+ * condition + arm-value uses are already harvested onto the branch's blocks).
285
+ * The arms are now control-dependent on the branch condition, and `x` is defined
286
+ * at the join — mirrors the Rust visitor's value-position `let` handling.
287
+ */
288
+ visitBindBranch(stmt, branch) {
289
+ const res = this.visitBranchExpr(branch);
290
+ const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.assignmentDefFacts(stmt));
291
+ this.builder.connect(res.exits, cont, 'seq');
292
+ return { entry: res.entry, exits: [cont] };
293
+ }
294
+ visitIf(stmt) {
295
+ const inverted = stmt.type === 'unless';
296
+ const cond = stmt.childForFieldName('condition') ?? stmt;
297
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
298
+ const thenKind = inverted ? 'cond-false' : 'cond-true';
299
+ const elseKind = inverted ? 'cond-true' : 'cond-false';
300
+ const exits = [];
301
+ const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
302
+ if (thenRes) {
303
+ this.builder.edge(header, thenRes.entry, thenKind);
304
+ exits.push(...thenRes.exits);
305
+ }
306
+ else {
307
+ exits.push(header); // empty consequence — that path falls through
308
+ }
309
+ const alt = stmt.childForFieldName('alternative');
310
+ if (alt) {
311
+ const { exits: altExits } = this.visitIfAlternative(alt, header, elseKind);
312
+ exits.push(...altExits);
313
+ }
314
+ else {
315
+ exits.push(header); // no else — the complement path falls through to the join
316
+ }
317
+ return { entry: header, exits: [...new Set(exits)] };
318
+ }
319
+ /**
320
+ * Thread an `if`/`unless` alternative: an `elsif` (its own condition chained on
321
+ * the parent's false edge) or a trailing `else`. Returns the dangling exits.
322
+ */
323
+ visitIfAlternative(alt, falseFrom, falseKind) {
324
+ const exits = [];
325
+ if (alt.type === 'elsif') {
326
+ const cond = alt.childForFieldName('condition') ?? alt;
327
+ const elifHeader = this.builder.newBlock(startLineOf(alt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
328
+ this.builder.edge(falseFrom, elifHeader, falseKind);
329
+ const thenRes = this.visitBody(alt.childForFieldName('consequence'));
330
+ if (thenRes) {
331
+ this.builder.edge(elifHeader, thenRes.entry, 'cond-true');
332
+ exits.push(...thenRes.exits);
333
+ }
334
+ else {
335
+ exits.push(elifHeader);
336
+ }
337
+ const nested = alt.childForFieldName('alternative');
338
+ if (nested) {
339
+ exits.push(...this.visitIfAlternative(nested, elifHeader, 'cond-false').exits);
340
+ }
341
+ else {
342
+ exits.push(elifHeader); // elsif with no further alternative falls through
343
+ }
344
+ }
345
+ else {
346
+ // a trailing `else` (consumes the false path entirely).
347
+ const elseRes = this.visitBody(alt);
348
+ if (elseRes) {
349
+ this.builder.edge(falseFrom, elseRes.entry, falseKind);
350
+ exits.push(...elseRes.exits);
351
+ }
352
+ else {
353
+ exits.push(falseFrom);
354
+ }
355
+ }
356
+ return { exits };
357
+ }
358
+ /**
359
+ * `expr if cond` / `expr unless cond` — the modifier statement-form. The body
360
+ * runs on the taken branch; the complement falls straight through to the join.
361
+ */
362
+ visitIfModifier(stmt) {
363
+ const inverted = stmt.type === 'unless_modifier';
364
+ const cond = stmt.childForFieldName('condition');
365
+ const body = stmt.childForFieldName('body');
366
+ const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : stmt.text, 'normal', cond ? this.harvest.facts(cond) : undefined);
367
+ const takenKind = inverted ? 'cond-false' : 'cond-true';
368
+ const exits = [header]; // the complement path falls through
369
+ const bodyRes = body ? this.visitStmt(body) : null;
370
+ if (bodyRes) {
371
+ this.builder.edge(header, bodyRes.entry, takenKind);
372
+ exits.push(...bodyRes.exits);
373
+ }
374
+ else {
375
+ this.builder.edge(header, header, takenKind === 'cond-true' ? 'cond-true' : 'cond-false');
376
+ }
377
+ return { entry: header, exits: [...new Set(exits)] };
378
+ }
379
+ /**
380
+ * `while cond … end` / `until cond … end`. `until` inverts (its body runs while
381
+ * the condition is FALSE); both are modeled with `cond-true` to the body and
382
+ * `cond-false` to the loop exit (the inversion is a semantic note, not a
383
+ * structural change — the header is a single control point either way).
384
+ */
385
+ visitWhile(stmt) {
386
+ const cond = stmt.childForFieldName('condition') ?? stmt;
387
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
388
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
389
+ this.cfc.pushLoop(header, loopExit, []);
390
+ const body = this.visitBody(stmt.childForFieldName('body'));
391
+ this.cfc.pop();
392
+ if (body) {
393
+ this.builder.edge(header, body.entry, 'cond-true');
394
+ this.builder.connect(body.exits, header, 'loop-back');
395
+ }
396
+ else {
397
+ this.builder.edge(header, header, 'loop-back'); // empty body re-tests
398
+ }
399
+ // Always emit the structural exit edge — even `while true` keeps EXIT
400
+ // reverse-reachable for the post-dominator / CDG pass.
401
+ this.builder.edge(header, loopExit, 'cond-false');
402
+ return { entry: header, exits: [loopExit] };
403
+ }
404
+ /** `expr while cond` / `expr until cond` — the modifier loop-form. */
405
+ visitWhileModifier(stmt) {
406
+ const cond = stmt.childForFieldName('condition') ?? stmt;
407
+ const body = stmt.childForFieldName('body');
408
+ const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
409
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
410
+ this.cfc.pushLoop(header, loopExit, []);
411
+ const bodyRes = body ? this.visitStmt(body) : null;
412
+ this.cfc.pop();
413
+ if (bodyRes) {
414
+ this.builder.edge(header, bodyRes.entry, 'cond-true');
415
+ this.builder.connect(bodyRes.exits, header, 'loop-back');
416
+ }
417
+ else {
418
+ this.builder.edge(header, header, 'loop-back');
419
+ }
420
+ this.builder.edge(header, loopExit, 'cond-false');
421
+ return { entry: header, exits: [loopExit] };
422
+ }
423
+ /**
424
+ * `for PATTERN in VALUE … end`. Header = the iteration test (a def of the
425
+ * pattern target(s) + a use of the iterable).
426
+ */
427
+ visitFor(stmt) {
428
+ const value = stmt.childForFieldName('value');
429
+ const header = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.loopHeadFacts(stmt));
430
+ const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
431
+ this.cfc.pushLoop(header, loopExit, []);
432
+ const body = this.visitBody(stmt.childForFieldName('body'));
433
+ this.cfc.pop();
434
+ if (body) {
435
+ this.builder.edge(header, body.entry, 'cond-true');
436
+ this.builder.connect(body.exits, header, 'loop-back');
437
+ }
438
+ else {
439
+ this.builder.edge(header, header, 'loop-back');
440
+ }
441
+ this.builder.edge(header, loopExit, 'cond-false');
442
+ return { entry: header, exits: [loopExit] };
443
+ }
444
+ forHeaderText(stmt) {
445
+ const pattern = stmt.childForFieldName('pattern')?.text ?? '';
446
+ const value = stmt.childForFieldName('value')?.text ?? '';
447
+ return pattern || value ? `for ${pattern} ${value}` : stmt.text.split('\n')[0];
448
+ }
449
+ /**
450
+ * `case VALUE; when P; … else; … end`. Cases do NOT fall through (like Go /
451
+ * Python match). The subject block fans a `switch-case` edge to each `when`
452
+ * body; a `case` with no `else` also reaches the join directly (no-match path),
453
+ * keeping EXIT reverse-reachable.
454
+ */
455
+ visitCase(stmt) {
456
+ const value = stmt.childForFieldName('value');
457
+ const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `case ${value.text}` : 'case', 'normal', value ? this.harvest.facts(value) : undefined);
458
+ const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
459
+ const whenClauses = stmt.namedChildren.filter((c) => c.type === 'when');
460
+ const elseClause = stmt.namedChildren.find((c) => c.type === 'else');
461
+ // Each `when` pattern evaluates conditionally before its body — harvest its
462
+ // uses onto the dispatch block (a later case only tests when earlier
463
+ // patterns did not match).
464
+ for (const w of whenClauses) {
465
+ for (const pat of this.whenPatterns(w)) {
466
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(pat));
467
+ }
468
+ }
469
+ this.cfc.pushSwitch(caseExit, []);
470
+ for (const w of whenClauses) {
471
+ const bodyRes = this.visitBody(w.childForFieldName('body'));
472
+ const entry = bodyRes?.entry ?? caseExit;
473
+ this.builder.edge(dispatch, entry, 'switch-case');
474
+ if (bodyRes)
475
+ this.builder.connect(bodyRes.exits, caseExit, 'seq');
476
+ }
477
+ if (elseClause) {
478
+ const elseRes = this.visitBody(elseClause);
479
+ const entry = elseRes?.entry ?? caseExit;
480
+ this.builder.edge(dispatch, entry, 'switch-case');
481
+ if (elseRes)
482
+ this.builder.connect(elseRes.exits, caseExit, 'seq');
483
+ }
484
+ else {
485
+ // No `else` → a no-match path reaches the exit directly (keeps EXIT
486
+ // reverse-reachable even when every when body jumps).
487
+ this.builder.edge(dispatch, caseExit, 'switch-case');
488
+ }
489
+ this.cfc.pop();
490
+ return { entry: dispatch, exits: [caseExit] };
491
+ }
492
+ /** The `pattern`-field children of a `when` clause (a `when` may list several). */
493
+ whenPatterns(whenClause) {
494
+ const out = [];
495
+ for (let i = 0; i < whenClause.childCount; i++) {
496
+ if (whenClause.fieldNameForChild(i) === 'pattern') {
497
+ const c = whenClause.child(i);
498
+ if (c)
499
+ out.push(c);
500
+ }
501
+ }
502
+ return out;
503
+ }
504
+ /**
505
+ * `case VALUE; in PATTERN [if guard]; … else; … end` — the pattern-matching
506
+ * form (`case_match`). Same no-fallthrough dispatch as `case`/`when`; each
507
+ * `in_clause` body is dispatched with a `switch-case` edge and an `in_clause`
508
+ * guard is harvested as a conditional use on the dispatch.
509
+ */
510
+ visitCaseMatch(stmt) {
511
+ const value = stmt.childForFieldName('value');
512
+ const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `case ${value.text}` : 'case', 'normal', value ? this.harvest.facts(value) : undefined);
513
+ const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
514
+ const clauses = stmt.namedChildren.filter((c) => c.type === 'in_clause');
515
+ const elseClause = stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
516
+ // An `in`-clause guard (`in P if g`) evaluates conditionally on the dispatch.
517
+ for (const c of clauses) {
518
+ const guard = c.childForFieldName('guard');
519
+ if (guard)
520
+ this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
521
+ }
522
+ this.cfc.pushSwitch(caseExit, []);
523
+ for (const c of clauses) {
524
+ const bodyRes = this.visitBody(c.childForFieldName('body'));
525
+ const entry = bodyRes?.entry ?? caseExit;
526
+ this.builder.edge(dispatch, entry, 'switch-case');
527
+ if (bodyRes)
528
+ this.builder.connect(bodyRes.exits, caseExit, 'seq');
529
+ }
530
+ if (elseClause) {
531
+ const elseRes = this.visitBody(elseClause);
532
+ const entry = elseRes?.entry ?? caseExit;
533
+ this.builder.edge(dispatch, entry, 'switch-case');
534
+ if (elseRes)
535
+ this.builder.connect(elseRes.exits, caseExit, 'seq');
536
+ }
537
+ else {
538
+ // A `case … in` with no `else` raises NoMatchingPatternError on no match,
539
+ // but the structural no-match edge keeps EXIT reverse-reachable.
540
+ this.builder.edge(dispatch, caseExit, 'switch-case');
541
+ }
542
+ this.cfc.pop();
543
+ return { entry: dispatch, exits: [caseExit] };
544
+ }
545
+ /**
546
+ * `begin … [rescue …]* [else …] [ensure …] end`. Mirrors the Python
547
+ * try-route-through:
548
+ * - `ensure` runs on every exit (normal, exception, early jumps) — a finalizer
549
+ * frame for early-exit threading + a normal/exceptional join.
550
+ * - each `rescue` handler catches from the protected body.
551
+ * - `else` runs only if the body completed with no exception.
552
+ */
553
+ visitBegin(stmt) {
554
+ const children = this.statementsOf(stmt);
555
+ return this.buildProtected(stmt, children);
556
+ }
557
+ /**
558
+ * A method `body_statement` is an implicit `begin` — its trailing
559
+ * `rescue`/`else`/`ensure` children behave exactly like a `begin`'s. When there
560
+ * are none, this is just a straight statement sequence.
561
+ */
562
+ visitImplicitBegin(stmt) {
563
+ const children = this.statementsOf(stmt);
564
+ const hasHandlers = children.some((c) => c.type === 'rescue' || c.type === 'else' || c.type === 'ensure');
565
+ if (!hasHandlers)
566
+ return this.visitSeq(children);
567
+ return this.buildProtected(stmt, children);
568
+ }
569
+ /**
570
+ * Shared begin/rescue/else/ensure builder. `children` is the construct's
571
+ * ordered named children: leading protected statements, then the typed
572
+ * `rescue` / `else` / `ensure` clauses.
573
+ */
574
+ buildProtected(span, children) {
575
+ const protectedStmts = [];
576
+ const rescueClauses = [];
577
+ let elseClause;
578
+ let ensureClause;
579
+ for (const c of children) {
580
+ if (c.type === 'rescue')
581
+ rescueClauses.push(c);
582
+ else if (c.type === 'else')
583
+ elseClause = c;
584
+ else if (c.type === 'ensure')
585
+ ensureClause = c;
586
+ else
587
+ protectedStmts.push(c);
588
+ }
589
+ // Build ensure first — it is both a normal join and a handler target. It runs
590
+ // OUTSIDE this begin's finalizer frame (a return inside ensure threads only
591
+ // OUTER ensures).
592
+ const ensureRes = ensureClause ? this.visitSeq(this.statementsOf(ensureClause)) : null;
593
+ const finFrame = ensureRes ? this.cfc.pushFinalizer(ensureRes.entry) : null;
594
+ // Pre-create each rescue's facts-only HEAD block (`rescue Exc => e` binds `e`
595
+ // once, on handler entry) so the protected body knows its handler target
596
+ // BEFORE either body is walked — this lets a `retry` inside a rescue handler
597
+ // re-enter the protected body's real entry (which is walked next).
598
+ const handlerEntries = rescueClauses.map((clause) => this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', this.harvest.rescueHeadFacts(clause)));
599
+ // Handler for the protected body: the first rescue if present, else ensure,
600
+ // else the outer handler.
601
+ const tryHandler = handlerEntries[0] ?? ensureRes?.entry ?? this.currentHandler();
602
+ const protectedStart = this.builder.blockCount;
603
+ this.handlers.push(tryHandler);
604
+ const bodyRes = this.visitSeq(protectedStmts);
605
+ this.handlers.pop();
606
+ // Block range covering ONLY the protected body (rescue handler bodies are
607
+ // walked afterward and must NOT get the conservative per-block throw edge).
608
+ const protectedEnd = this.builder.blockCount;
609
+ // The protected body's entry is where a `retry` re-enters (a `loop-back`).
610
+ // Push it for BOTH the rescue-handler bodies AND already-walked body (already
611
+ // done above — a `retry` in the body itself is a non-idiom, but harmless).
612
+ const beginEntry = bodyRes?.entry ?? handlerEntries[0] ?? this.builder.exitIndex;
613
+ this.beginEntries.push(beginEntry);
614
+ // Walk each rescue handler body now (within the begin-entry scope so a
615
+ // `retry` re-enters the protected body). A raise inside a handler propagates
616
+ // to ensure (if any), else the outer handler.
617
+ const handlerExits = [];
618
+ rescueClauses.forEach((clause, i) => {
619
+ if (ensureRes)
620
+ this.handlers.push(ensureRes.entry);
621
+ const headBlock = handlerEntries[i];
622
+ const handlerBodyRes = this.visitBody(clause.childForFieldName('body'));
623
+ if (handlerBodyRes) {
624
+ this.builder.edge(headBlock, handlerBodyRes.entry, 'seq');
625
+ handlerExits.push(...handlerBodyRes.exits);
626
+ }
627
+ else {
628
+ handlerExits.push(headBlock); // empty handler — header is the exit
629
+ }
630
+ if (ensureRes)
631
+ this.handlers.pop();
632
+ });
633
+ this.beginEntries.pop();
634
+ // Conservative exceptional edges: ANY protected-region block may raise to
635
+ // EACH handler (an unmatched exception type tries the next handler).
636
+ if (rescueClauses.length > 0 || ensureClause) {
637
+ const targets = handlerEntries.length > 0 ? handlerEntries : ensureRes ? [ensureRes.entry] : [];
638
+ for (let b = protectedStart; b < protectedEnd; b++) {
639
+ for (const h of targets)
640
+ this.builder.edge(b, h, 'throw');
641
+ }
642
+ }
643
+ // The `else` runs only on no-exception normal completion of the body.
644
+ let normalAfterBody = bodyRes ? [...bodyRes.exits] : [];
645
+ if (elseClause) {
646
+ const elseRes = this.visitBody(elseClause);
647
+ if (elseRes && bodyRes) {
648
+ this.builder.connect(bodyRes.exits, elseRes.entry, 'seq');
649
+ normalAfterBody = [...elseRes.exits];
650
+ }
651
+ else if (elseRes) {
652
+ normalAfterBody = [...elseRes.exits];
653
+ }
654
+ }
655
+ // Close the finalizer frame; wire crossing-jump completion legs.
656
+ if (finFrame && ensureRes) {
657
+ this.cfc.pop();
658
+ drainFinalizerPending(this.builder, finFrame, ensureRes.exits);
659
+ }
660
+ const exits = [];
661
+ if (ensureRes) {
662
+ // Normal completion of (body→else) AND each handler flows through ensure.
663
+ this.builder.connect(normalAfterBody, ensureRes.entry, 'seq');
664
+ this.builder.connect(handlerExits, ensureRes.entry, 'seq');
665
+ exits.push(...ensureRes.exits);
666
+ // A begin with no rescue → an uncaught exception re-propagates after ensure.
667
+ if (handlerEntries.length === 0) {
668
+ this.builder.connect(ensureRes.exits, this.currentHandler(), 'throw');
669
+ }
670
+ }
671
+ else {
672
+ exits.push(...normalAfterBody);
673
+ exits.push(...handlerExits);
674
+ }
675
+ const entry = bodyRes?.entry ?? ensureRes?.entry ?? handlerEntries[0];
676
+ if (entry === undefined) {
677
+ void span;
678
+ return null;
679
+ }
680
+ return { entry, exits: [...new Set(exits)] };
681
+ }
682
+ /** Nearest enclosing exception handler, or the function EXIT. */
683
+ currentHandler() {
684
+ return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
685
+ }
686
+ }
687
+ /** The body node of a Ruby function/block/lambda (a `block` for `lambda` unwraps once). */
688
+ function functionBody(fnNode) {
689
+ const body = fnNode.childForFieldName('body');
690
+ if (!body)
691
+ return undefined;
692
+ // A `lambda`'s `body` is a `block` wrapping a `block_body` — unwrap to the
693
+ // inner body so the walk sees the statement sequence.
694
+ if (fnNode.type === 'lambda' && body.type === 'block') {
695
+ return body.childForFieldName('body') ?? body;
696
+ }
697
+ return body;
698
+ }
699
+ /** Build the CFG for one Ruby function/block/lambda node, or `undefined` if not modelable. */
700
+ function buildFunctionCfg(fnNode, filePath) {
701
+ try {
702
+ if (!RUBY_FUNCTION_TYPES.has(fnNode.type))
703
+ return undefined;
704
+ const startLine = startLineOf(fnNode);
705
+ const endLine = endLineOf(fnNode);
706
+ const startColumn = fnNode.startPosition.column;
707
+ const body = functionBody(fnNode);
708
+ if (!body) {
709
+ // No body — an empty `def f; end` still gets a trivial ENTRY → EXIT CFG so
710
+ // it is not silently dropped.
711
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
712
+ const harvest = new RubyHarvester(fnNode);
713
+ const paramFacts = harvest.paramFacts();
714
+ if (paramFacts)
715
+ builder.attachFacts(builder.entryIndex, paramFacts);
716
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq');
717
+ return builder.finish(harvest.bindingTable());
718
+ }
719
+ const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
720
+ const harvest = new RubyHarvester(fnNode);
721
+ const paramFacts = harvest.paramFacts();
722
+ if (paramFacts)
723
+ builder.attachFacts(builder.entryIndex, paramFacts);
724
+ const walk = new RubyCfgWalk(builder, harvest);
725
+ // For a bare block / lambda, a `redo` re-enters the block body. The body's
726
+ // first block is the NEXT index the builder will allocate (entry + exit are
727
+ // already taken). Set it so a top-level `redo` inside the block loops back.
728
+ if (fnNode.type !== 'method' && fnNode.type !== 'singleton_method') {
729
+ walk.setBlockRedoTarget(builder.blockCount);
730
+ }
731
+ const res = walk.visitStmt(body);
732
+ if (!res) {
733
+ builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
734
+ return builder.finish(harvest.bindingTable());
735
+ }
736
+ builder.edge(builder.entryIndex, res.entry, 'seq');
737
+ builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
738
+ return builder.finish(harvest.bindingTable());
739
+ }
740
+ catch (err) {
741
+ // Never throw out of buildFunctionCfg — a malformed AST shape must skip only
742
+ // this one function's CFG, never drop the whole file's language group (R4).
743
+ // eslint-disable-next-line no-console
744
+ console.warn(`[cfg] Ruby buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
745
+ return undefined;
746
+ }
747
+ }
748
+ /** Whether a node is a Ruby function/closure this visitor builds a CFG for. */
749
+ function isFunction(node) {
750
+ return RUBY_FUNCTION_TYPES.has(node.type);
751
+ }
752
+ /** The Ruby CFG visitor. */
753
+ export function createRubyCfgVisitor() {
754
+ return { buildFunctionCfg, isFunction };
755
+ }
756
+ export { RUBY_FUNCTION_TYPES };