gitnexus 1.6.8-rc.9 → 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 (430) 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 +11 -0
  135. package/dist/core/ingestion/languages/c-cpp.js +6 -1
  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 +3 -0
  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 +23 -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/parse-worker.d.ts +13 -0
  238. package/dist/core/ingestion/workers/parse-worker.js +76 -17
  239. package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
  240. package/dist/core/ingestion/workers/worker-pool.js +15 -2
  241. package/dist/core/lbug/csv-generator.d.ts +51 -3
  242. package/dist/core/lbug/csv-generator.js +369 -255
  243. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  244. package/dist/core/lbug/lbug-adapter.js +346 -84
  245. package/dist/core/lbug/lbug-config.d.ts +19 -0
  246. package/dist/core/lbug/lbug-config.js +29 -0
  247. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  248. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  249. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  250. package/dist/core/lbug/pool-adapter.js +90 -1
  251. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  252. package/dist/core/lbug/rel-pair-routing.js +140 -0
  253. package/dist/core/lbug/schema.d.ts +2 -2
  254. package/dist/core/lbug/schema.js +3 -0
  255. package/dist/core/logger.d.ts +0 -5
  256. package/dist/core/run-analyze.d.ts +96 -0
  257. package/dist/core/run-analyze.js +207 -3
  258. package/dist/core/tree-sitter/parser-loader.js +5 -4
  259. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  260. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  261. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  262. package/dist/core/wiki/generator.js +3 -1
  263. package/dist/core/wiki/graph-queries.d.ts +6 -0
  264. package/dist/core/wiki/graph-queries.js +9 -1
  265. package/dist/mcp/http-transport.d.ts +134 -0
  266. package/dist/mcp/http-transport.js +503 -0
  267. package/dist/mcp/local/local-backend.d.ts +110 -24
  268. package/dist/mcp/local/local-backend.js +1225 -19
  269. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  270. package/dist/mcp/local/pdg-impact.js +1972 -0
  271. package/dist/mcp/resources.js +13 -1
  272. package/dist/mcp/tools.d.ts +12 -0
  273. package/dist/mcp/tools.js +214 -7
  274. package/dist/server/analyze-launch.d.ts +29 -0
  275. package/dist/server/analyze-launch.js +137 -0
  276. package/dist/server/analyze-upload.d.ts +33 -0
  277. package/dist/server/analyze-upload.js +123 -0
  278. package/dist/server/analyze-worker.d.ts +17 -1
  279. package/dist/server/api.d.ts +17 -0
  280. package/dist/server/api.js +132 -168
  281. package/dist/server/git-clone.d.ts +43 -2
  282. package/dist/server/git-clone.js +211 -16
  283. package/dist/server/mcp-http.d.ts +5 -6
  284. package/dist/server/mcp-http.js +9 -82
  285. package/dist/server/middleware.d.ts +40 -0
  286. package/dist/server/middleware.js +92 -0
  287. package/dist/server/private-ip.d.ts +1 -0
  288. package/dist/server/private-ip.js +15 -0
  289. package/dist/server/upload-ingest.d.ts +56 -0
  290. package/dist/server/upload-ingest.js +276 -0
  291. package/dist/server/upload-paths.d.ts +37 -0
  292. package/dist/server/upload-paths.js +57 -0
  293. package/dist/server/upload-sweep.d.ts +20 -0
  294. package/dist/server/upload-sweep.js +57 -0
  295. package/dist/storage/git.js +5 -8
  296. package/dist/storage/parse-cache.d.ts +22 -1
  297. package/dist/storage/parse-cache.js +41 -10
  298. package/dist/storage/repo-manager.d.ts +121 -10
  299. package/dist/storage/repo-manager.js +43 -5
  300. package/dist/types/pipeline.d.ts +9 -0
  301. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  302. package/hooks/claude/gitnexus-hook.cjs +110 -13
  303. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  304. package/package.json +8 -2
  305. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  306. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  307. package/scripts/cross-platform-tests.ts +1 -0
  308. package/skills/gitnexus-debugging.md +16 -4
  309. package/skills/gitnexus-exploring.md +3 -3
  310. package/skills/gitnexus-guide.md +33 -0
  311. package/skills/gitnexus-pdg-query.md +89 -0
  312. package/skills/gitnexus-refactoring.md +1 -1
  313. package/skills/gitnexus-taint-analysis.md +178 -0
  314. package/vendor/tree-sitter-c/package.json +1 -1
  315. package/vendor/tree-sitter-dart/package.json +1 -1
  316. package/vendor/tree-sitter-kotlin/package.json +1 -1
  317. package/vendor/tree-sitter-proto/package.json +1 -1
  318. package/vendor/tree-sitter-swift/package.json +1 -1
  319. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  320. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  321. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  322. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  323. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  324. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  325. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  326. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  327. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  328. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  329. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  330. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  331. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  332. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  333. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  334. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  335. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  336. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  337. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  338. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  339. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  340. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  341. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  342. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  343. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  344. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  345. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  346. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  347. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  348. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  349. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  350. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  351. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  352. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  353. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  354. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  355. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  356. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  357. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  358. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  359. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  360. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  361. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  362. package/web/assets/index-BvYev1UJ.js +635 -0
  363. package/web/assets/index-_lgn7hs5.css +2 -0
  364. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  365. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  366. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  367. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  368. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  369. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  370. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  371. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  372. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  373. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  374. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  375. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  376. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  377. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  378. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  379. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  380. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  381. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  382. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  383. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  384. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  385. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  386. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  387. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  388. package/web/index.html +13 -15
  389. package/scripts/materialize-vendor-grammars.cjs +0 -97
  390. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  391. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  392. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  393. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  394. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  395. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  396. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  397. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  398. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  399. package/web/assets/chunk-CilyBKbf.js +0 -1
  400. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  401. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  402. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  403. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  404. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  405. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  406. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  407. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  408. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  409. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  410. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  411. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  412. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  413. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  414. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  415. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  416. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  417. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  418. package/web/assets/index-BKWA-m7o.css +0 -2
  419. package/web/assets/index-CG6q8eTs.js +0 -626
  420. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  421. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  422. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  423. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  424. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  425. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  426. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  427. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  428. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  429. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  430. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,1056 @@
1
+ /** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
2
+ const NESTED_FUNCTION_TYPES = new Set([
3
+ 'function_declaration',
4
+ 'function_expression',
5
+ 'arrow_function',
6
+ 'method_definition',
7
+ 'generator_function_declaration',
8
+ 'generator_function',
9
+ 'async_function_declaration',
10
+ 'async_arrow_function',
11
+ ]);
12
+ /** Function-ish declaration statements whose NAME still binds in the enclosing scope. */
13
+ const FUNCTION_DECL_TYPES = new Set([
14
+ 'function_declaration',
15
+ 'generator_function_declaration',
16
+ 'async_function_declaration',
17
+ ]);
18
+ /**
19
+ * Nodes that open a lexical scope for `let`/`const`/`class`/catch bindings.
20
+ * A `switch` BODY is deliberately ONE scope shared by all case arms (JS
21
+ * semantics: `case 1: let x = 1; case 2: use(x)` is the same binding).
22
+ */
23
+ const SCOPE_TYPES = new Set([
24
+ 'statement_block',
25
+ 'for_statement',
26
+ 'for_in_statement',
27
+ 'for_of_statement',
28
+ 'catch_clause',
29
+ 'switch_body',
30
+ ]);
31
+ /** Type-position subtrees — identifiers inside them are not value uses. */
32
+ const TYPE_CONTEXT_TYPES = new Set([
33
+ 'type_annotation',
34
+ 'type_arguments',
35
+ 'type_parameters',
36
+ 'type_predicate_annotation',
37
+ 'asserts_annotation',
38
+ ]);
39
+ /**
40
+ * Wrappers that don't change which VALUE flows through them (#2083 M3 U1) —
41
+ * unwrapped when resolving call-result attribution (`const b = (await
42
+ * escape(t))!` still attaches `resultDefs: [b]` to the escape site) and
43
+ * member-chain roots. Distinct from {@link TsHarvester.unwrapLvalue}, which is
44
+ * the narrower LVALUE set.
45
+ */
46
+ const VALUE_WRAPPER_TYPES = new Set([
47
+ 'parenthesized_expression',
48
+ 'non_null_expression',
49
+ 'as_expression',
50
+ 'satisfies_expression',
51
+ 'await_expression',
52
+ ]);
53
+ /** Literal text of a `string` node (concatenated fragments; raw escapes kept). */
54
+ const stringLiteralText = (node) => {
55
+ let out = '';
56
+ for (let i = 0; i < node.namedChildCount; i++) {
57
+ const c = node.namedChild(i);
58
+ if (c?.type === 'string_fragment' || c?.type === 'escape_sequence')
59
+ out += c.text;
60
+ }
61
+ return out;
62
+ };
63
+ export class TsHarvester {
64
+ fnNode;
65
+ bindings = [];
66
+ /** Scope-opening node id → its scope. */
67
+ scopeByNode = new Map();
68
+ root = { parent: null, table: new Map() };
69
+ /** name → synthetic binding index (implicit global / import / captured). */
70
+ synthetic = new Map();
71
+ fnId;
72
+ /**
73
+ * Innermost enclosing scope per visited node id, filled during the prescan
74
+ * (which already touches every named node once). Makes phase-2 resolution
75
+ * O(scope-chain) instead of O(AST-depth) per identifier — a deeply-chained
76
+ * single-statement expression (generated code) otherwise turns the
77
+ * parent-chain walk quadratic (tri-review perf finding).
78
+ */
79
+ nearestScopeCache = new Map();
80
+ /**
81
+ * >0 while walking a conditionally-evaluated subexpression (short-circuit
82
+ * right operand, ternary arm, logical-assignment target, case test). Defs
83
+ * found there are MAY-defs — gen without kill (tri-review P1: a must-def
84
+ * here falsely kills the prior def on the not-taken path).
85
+ */
86
+ conditionalDepth = 0;
87
+ /**
88
+ * Call/new node id → bindings whose declarator/assignment VALUE is exactly
89
+ * that call (#2083 M3 U1). Registered by the declarator/assignment handlers
90
+ * BEFORE the value walk, consumed by {@link visitCall} when it reaches the
91
+ * node — the indirection keeps result-def attribution per-declarator
92
+ * (`const a = t, b = escape(t)` attaches `[b]` to the escape site only) and
93
+ * top-level-only (`const c = cond ? escape(b) : b` attaches nothing — the
94
+ * bypass occurrence must keep `c` taintable, plan KTD4a).
95
+ */
96
+ resultDefTargets = new Map();
97
+ constructor(fnNode) {
98
+ this.fnNode = fnNode;
99
+ this.fnId = fnNode.id;
100
+ this.scopeByNode.set(fnNode.id, this.root);
101
+ this.declareParams(fnNode);
102
+ const body = fnNode.childForFieldName('body');
103
+ if (body)
104
+ this.prescan(body, body.type === 'statement_block' ? this.openScope(body) : this.root);
105
+ }
106
+ /** The completed binding table — pass to `CfgBuilder.finish`. */
107
+ table() {
108
+ return this.bindings;
109
+ }
110
+ // ── phase 1: declaration pre-scan ────────────────────────────────────────
111
+ openScope(node) {
112
+ const existing = this.scopeByNode.get(node.id);
113
+ if (existing)
114
+ return existing;
115
+ const scope = { parent: this.nearestScopeOf(node), table: new Map() };
116
+ this.scopeByNode.set(node.id, scope);
117
+ return scope;
118
+ }
119
+ nearestScopeOf(node) {
120
+ for (let p = node.parent; p; p = p.parent) {
121
+ const s = this.scopeByNode.get(p.id);
122
+ if (s)
123
+ return s;
124
+ if (p.id === this.fnId)
125
+ break;
126
+ }
127
+ return this.root;
128
+ }
129
+ declare(nameNode, kind, scope, hoistToRoot, formalIndex) {
130
+ const target = hoistToRoot ? this.root : scope;
131
+ const name = nameNode.text;
132
+ // `var` multi-declaration (and a param + `var` of the same name) is ONE
133
+ // binding — first declaration in source order is canonical. The dedup is
134
+ // scoped to the single target table, so an inner `let x` shadowing a root
135
+ // `var x` still gets its own entry in its own scope.
136
+ if (target.table.has(name))
137
+ return;
138
+ target.table.set(name, this.bindings.length);
139
+ this.bindings.push({
140
+ name,
141
+ declLine: nameNode.startPosition.row + 1,
142
+ declColumn: nameNode.startPosition.column,
143
+ kind,
144
+ // Carry the enclosing formal position for params so the PDG call-summary
145
+ // consumer joins by FORMAL slot, never the flattened binding ordinal. A
146
+ // destructured/rest formal hands the SAME index to every inner name.
147
+ ...(formalIndex !== undefined ? { formalIndex } : {}),
148
+ });
149
+ }
150
+ declareParams(fnNode) {
151
+ const params = fnNode.childForFieldName('parameters') ?? fnNode.childForFieldName('parameter');
152
+ if (!params)
153
+ return;
154
+ if (params.type === 'identifier') {
155
+ this.declare(params, 'param', this.root, true, 0); // `x => …` single-param arrow ⇒ formal 0
156
+ return;
157
+ }
158
+ // Each NAMED child of the parameter list is one top-level formal — its index
159
+ // here is the 0-based formal position. Destructured/rest formals fan out to
160
+ // several inner names, but ALL inherit this one formal index, so the PDG
161
+ // call-summary never misattributes an inner name to a later formal's slot.
162
+ let formalIndex = 0;
163
+ for (let i = 0; i < params.namedChildCount; i++) {
164
+ const p = params.namedChild(i);
165
+ if (!p)
166
+ continue;
167
+ // TS wraps each param (required_parameter/optional_parameter, field
168
+ // `pattern`); plain JS puts the pattern directly in formal_parameters.
169
+ const pattern = p.childForFieldName('pattern') ?? p;
170
+ this.declarePattern(pattern, 'param', this.root, true, formalIndex);
171
+ formalIndex++;
172
+ }
173
+ }
174
+ /**
175
+ * Declare every name bound by a (possibly destructuring) pattern. When
176
+ * `formalIndex` is supplied (param patterns), EVERY name the pattern binds
177
+ * carries that one enclosing-formal position (the recursion never reassigns
178
+ * it), so `function f({a, b}, c)` records a:0, b:0, c:1.
179
+ */
180
+ declarePattern(node, kind, scope, hoistToRoot, formalIndex) {
181
+ switch (node.type) {
182
+ case 'identifier':
183
+ case 'shorthand_property_identifier_pattern':
184
+ this.declare(node, kind, scope, hoistToRoot, formalIndex);
185
+ return;
186
+ case 'rest_pattern':
187
+ case 'object_pattern':
188
+ case 'array_pattern':
189
+ for (let i = 0; i < node.namedChildCount; i++) {
190
+ const c = node.namedChild(i);
191
+ if (c)
192
+ this.declarePattern(c, kind, scope, hoistToRoot, formalIndex);
193
+ }
194
+ return;
195
+ case 'pair_pattern': {
196
+ const value = node.childForFieldName('value');
197
+ if (value)
198
+ this.declarePattern(value, kind, scope, hoistToRoot, formalIndex);
199
+ return;
200
+ }
201
+ case 'assignment_pattern':
202
+ case 'object_assignment_pattern': {
203
+ const left = node.childForFieldName('left');
204
+ if (left)
205
+ this.declarePattern(left, kind, scope, hoistToRoot, formalIndex);
206
+ return;
207
+ }
208
+ default:
209
+ // Type annotations / unknown wrappers — descend defensively.
210
+ for (let i = 0; i < node.namedChildCount; i++) {
211
+ const c = node.namedChild(i);
212
+ if (c && !TYPE_CONTEXT_TYPES.has(c.type)) {
213
+ this.declarePattern(c, kind, scope, hoistToRoot, formalIndex);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ prescan(node, scope) {
219
+ this.nearestScopeCache.set(node.id, scope);
220
+ const t = node.type;
221
+ if (NESTED_FUNCTION_TYPES.has(t) && node.id !== this.fnId) {
222
+ // A nested function's NAME binds in the enclosing scope; its body is opaque.
223
+ if (FUNCTION_DECL_TYPES.has(t)) {
224
+ const name = node.childForFieldName('name');
225
+ if (name)
226
+ this.declare(name, 'function', scope, false);
227
+ }
228
+ return;
229
+ }
230
+ let childScope = scope;
231
+ if (SCOPE_TYPES.has(t))
232
+ childScope = this.openScope(node);
233
+ switch (t) {
234
+ case 'lexical_declaration': {
235
+ const kind = node.child(0)?.type === 'const' ? 'const' : 'let';
236
+ this.declareDeclarators(node, kind, childScope, false);
237
+ break;
238
+ }
239
+ case 'variable_declaration':
240
+ this.declareDeclarators(node, 'var', childScope, true);
241
+ break;
242
+ case 'class_declaration': {
243
+ const name = node.childForFieldName('name');
244
+ if (name)
245
+ this.declare(name, 'class', childScope, false);
246
+ break;
247
+ }
248
+ case 'catch_clause': {
249
+ const param = node.childForFieldName('parameter');
250
+ if (param)
251
+ this.declarePattern(param, 'catch', childScope, false);
252
+ break;
253
+ }
254
+ case 'for_in_statement':
255
+ case 'for_of_statement': {
256
+ // `for (const x of xs)` — the `kind` keyword marks a declaration; a bare
257
+ // `for (x of xs)` left is an assignment, resolved at use time instead.
258
+ const kindNode = node.childForFieldName('kind');
259
+ const left = node.childForFieldName('left');
260
+ if (kindNode && left) {
261
+ const k = kindNode.type === 'var' ? 'var' : kindNode.type === 'const' ? 'const' : 'let';
262
+ this.declarePattern(left, k, childScope, k === 'var');
263
+ }
264
+ break;
265
+ }
266
+ default:
267
+ break;
268
+ }
269
+ for (let i = 0; i < node.namedChildCount; i++) {
270
+ const c = node.namedChild(i);
271
+ if (c)
272
+ this.prescan(c, childScope);
273
+ }
274
+ }
275
+ declareDeclarators(declNode, kind, scope, hoistToRoot) {
276
+ for (let i = 0; i < declNode.namedChildCount; i++) {
277
+ const d = declNode.namedChild(i);
278
+ if (d?.type !== 'variable_declarator')
279
+ continue;
280
+ const name = d.childForFieldName('name');
281
+ if (name)
282
+ this.declarePattern(name, kind, scope, hoistToRoot);
283
+ }
284
+ }
285
+ // ── phase 2: per-statement fact extraction ───────────────────────────────
286
+ /**
287
+ * Def/use facts for one statement (or construct-header expression) node.
288
+ * Safe from any walk order — resolution consults the completed scope tree.
289
+ */
290
+ facts(node) {
291
+ const acc = new FactAccumulator(node.startPosition.row + 1);
292
+ this.walkValue(node, acc);
293
+ return acc.finish();
294
+ }
295
+ /**
296
+ * Facts for an expression whose WHOLE evaluation is conditional (switch
297
+ * case tests, which only run when earlier cases didn't match) — every def
298
+ * inside becomes a may-def.
299
+ */
300
+ factsConditional(node) {
301
+ const acc = new FactAccumulator(node.startPosition.row + 1);
302
+ this.conditional(() => this.walkValue(node, acc));
303
+ return acc.finish();
304
+ }
305
+ /** Facts for a `for (left in/of right)` head: left binds/assigns, right is used. */
306
+ forInHeadFacts(stmt) {
307
+ const acc = new FactAccumulator(stmt.startPosition.row + 1);
308
+ const left = stmt.childForFieldName('left');
309
+ const right = stmt.childForFieldName('right');
310
+ if (left)
311
+ this.walkDefPattern(left, acc);
312
+ if (right)
313
+ this.walkValue(right, acc);
314
+ return acc.finish();
315
+ }
316
+ /** ENTRY-block facts for the function's parameters (defs + default-value uses). */
317
+ paramFacts() {
318
+ const fnNode = this.fnNode;
319
+ const params = fnNode.childForFieldName('parameters') ?? fnNode.childForFieldName('parameter');
320
+ if (!params)
321
+ return undefined;
322
+ const acc = new FactAccumulator(fnNode.startPosition.row + 1);
323
+ if (params.type === 'identifier') {
324
+ this.def(params, acc);
325
+ }
326
+ else {
327
+ for (let i = 0; i < params.namedChildCount; i++) {
328
+ const p = params.namedChild(i);
329
+ if (!p)
330
+ continue;
331
+ const pattern = p.childForFieldName('pattern') ?? p;
332
+ this.walkDefPattern(pattern, acc);
333
+ const dflt = p.childForFieldName('value');
334
+ if (dflt)
335
+ this.walkValue(dflt, acc);
336
+ }
337
+ }
338
+ return acc.defCount() || acc.useCount() ? acc.finish() : undefined;
339
+ }
340
+ /** Def fact for a `catch (e)` parameter — prepend to the handler entry block. */
341
+ catchParamFacts(catchClause) {
342
+ const param = catchClause.childForFieldName('parameter');
343
+ if (!param)
344
+ return undefined;
345
+ const acc = new FactAccumulator(catchClause.startPosition.row + 1);
346
+ this.walkDefPattern(param, acc);
347
+ return acc.defCount() ? acc.finish() : undefined;
348
+ }
349
+ resolve(nameNode) {
350
+ const name = nameNode.text;
351
+ // Fast path: the prescan cached every visited node's innermost scope, so
352
+ // resolution walks the SCOPE chain (shallow), not the AST parent chain
353
+ // (arbitrarily deep in chained expressions). The parent-chain walk remains
354
+ // as fallback for the few nodes the prescan never visits (e.g. a nested
355
+ // function declaration's own name node).
356
+ const cached = this.nearestScopeCache.get(nameNode.id);
357
+ let startScope = cached ?? null;
358
+ if (!startScope) {
359
+ for (let p = nameNode; p; p = p.parent) {
360
+ const scope = this.scopeByNode.get(p.id) ?? this.nearestScopeCache.get(p.id);
361
+ if (scope) {
362
+ startScope = scope;
363
+ break;
364
+ }
365
+ if (p.id === this.fnId) {
366
+ startScope = this.root;
367
+ break;
368
+ }
369
+ }
370
+ }
371
+ for (let s = startScope; s; s = s.parent) {
372
+ const idx = s.table.get(name);
373
+ if (idx !== undefined)
374
+ return idx;
375
+ }
376
+ // No in-function declaration — synthetic module-level binding, shared by
377
+ // defs and uses so `notDeclared = 1; use(notDeclared)` still forms a fact.
378
+ let idx = this.synthetic.get(name);
379
+ if (idx === undefined) {
380
+ idx = this.bindings.length;
381
+ this.synthetic.set(name, idx);
382
+ this.bindings.push({ name, declLine: 0, declColumn: 0, kind: 'module', synthetic: true });
383
+ }
384
+ return idx;
385
+ }
386
+ def(nameNode, acc) {
387
+ if (this.conditionalDepth > 0)
388
+ acc.addMayDef(this.resolve(nameNode));
389
+ else
390
+ acc.addDef(this.resolve(nameNode));
391
+ }
392
+ /** Run `fn` with defs demoted to may-defs (conditionally-evaluated context). */
393
+ conditional(fn) {
394
+ this.conditionalDepth++;
395
+ try {
396
+ fn();
397
+ }
398
+ finally {
399
+ this.conditionalDepth--;
400
+ }
401
+ }
402
+ /** Strip wrappers that don't change the lvalue (`(x) += 1`, `x! ++`). */
403
+ unwrapLvalue(node) {
404
+ let n = node;
405
+ while (n.type === 'parenthesized_expression' || n.type === 'non_null_expression') {
406
+ const inner = n.namedChild(0);
407
+ if (!inner)
408
+ break;
409
+ n = inner;
410
+ }
411
+ return n;
412
+ }
413
+ use(nameNode, acc) {
414
+ acc.addUse(this.resolve(nameNode));
415
+ }
416
+ /** Value-position walk: collect uses; route def positions to the pattern walk. */
417
+ walkValue(node, acc) {
418
+ const t = node.type;
419
+ if (TYPE_CONTEXT_TYPES.has(t))
420
+ return;
421
+ if (NESTED_FUNCTION_TYPES.has(t) && node.id !== this.fnId) {
422
+ // Opaque nested function: its NAME (function declaration) is a def in
423
+ // the enclosing scope; captured reads/writes inside are invisible (KTD4).
424
+ if (FUNCTION_DECL_TYPES.has(t)) {
425
+ const name = node.childForFieldName('name');
426
+ if (name)
427
+ this.def(name, acc);
428
+ }
429
+ return;
430
+ }
431
+ switch (t) {
432
+ case 'identifier':
433
+ case 'shorthand_property_identifier':
434
+ this.use(node, acc);
435
+ return;
436
+ case 'lexical_declaration':
437
+ case 'variable_declaration':
438
+ for (let i = 0; i < node.namedChildCount; i++) {
439
+ const d = node.namedChild(i);
440
+ if (d?.type !== 'variable_declarator')
441
+ continue;
442
+ const name = d.childForFieldName('name');
443
+ const value = d.childForFieldName('value');
444
+ // A bare `var x;` mid-function is hoisted and writes NOTHING at
445
+ // runtime — harvesting it as a def would fabricate a kill of the
446
+ // live def (`x = source(); var x; sink(x)` must keep source→sink;
447
+ // tri-review P2). `let`/`const` declarators genuinely initialize.
448
+ if (name && (value || t === 'lexical_declaration')) {
449
+ const snap = acc.defSnapshot();
450
+ this.walkDefPattern(name, acc);
451
+ if (value)
452
+ this.registerResultDefs(value, acc.defsSince(snap));
453
+ }
454
+ if (value)
455
+ this.walkValue(value, acc);
456
+ }
457
+ return;
458
+ case 'assignment_expression': {
459
+ const left = node.childForFieldName('left');
460
+ const right = node.childForFieldName('right');
461
+ if (left) {
462
+ const snap = acc.defSnapshot();
463
+ this.walkDefPattern(this.unwrapLvalue(left), acc);
464
+ if (right)
465
+ this.registerResultDefs(right, acc.defsSince(snap));
466
+ }
467
+ if (right)
468
+ this.walkValue(right, acc);
469
+ return;
470
+ }
471
+ case 'augmented_assignment_expression': {
472
+ // `x += y` both defines and uses x. The logical-assignment operators
473
+ // (`||=`, `&&=`, `??=`) only WRITE conditionally — their def is a
474
+ // may-def (the read always happens).
475
+ const left = node.childForFieldName('left')
476
+ ? this.unwrapLvalue(node.childForFieldName('left'))
477
+ : null;
478
+ const right = node.childForFieldName('right');
479
+ const op = node.childForFieldName('operator')?.type ?? '';
480
+ const logical = op === '||=' || op === '&&=' || op === '??=';
481
+ if (left?.type === 'identifier') {
482
+ if (logical)
483
+ this.conditional(() => this.def(left, acc));
484
+ else
485
+ this.def(left, acc);
486
+ this.use(left, acc);
487
+ }
488
+ else if (left) {
489
+ this.walkValue(left, acc); // member/subscript target — uses only
490
+ }
491
+ // The RHS of a logical assignment is itself conditionally evaluated.
492
+ if (right) {
493
+ if (logical)
494
+ this.conditional(() => this.walkValue(right, acc));
495
+ else
496
+ this.walkValue(right, acc);
497
+ }
498
+ return;
499
+ }
500
+ case 'update_expression': {
501
+ const rawArg = node.childForFieldName('argument');
502
+ const arg = rawArg ? this.unwrapLvalue(rawArg) : null;
503
+ if (arg?.type === 'identifier') {
504
+ this.def(arg, acc);
505
+ this.use(arg, acc);
506
+ }
507
+ else if (arg) {
508
+ this.walkValue(arg, acc);
509
+ }
510
+ return;
511
+ }
512
+ case 'binary_expression': {
513
+ // Short-circuit operators evaluate their RIGHT operand conditionally:
514
+ // a def inside it (`a && (x = clean())`, `c ?? (c = load())`) must be
515
+ // a may-def or the not-taken path's prior def is falsely killed
516
+ // (tri-review P1). Other binary operators evaluate both sides.
517
+ const left = node.childForFieldName('left');
518
+ const right = node.childForFieldName('right');
519
+ const op = node.childForFieldName('operator')?.type ?? '';
520
+ if (left)
521
+ this.walkValue(left, acc);
522
+ if (right) {
523
+ if (op === '&&' || op === '||' || op === '??') {
524
+ this.conditional(() => this.walkValue(right, acc));
525
+ }
526
+ else {
527
+ this.walkValue(right, acc);
528
+ }
529
+ }
530
+ return;
531
+ }
532
+ case 'ternary_expression': {
533
+ // Each arm is conditionally evaluated — defs inside are may-defs.
534
+ const cond = node.childForFieldName('condition');
535
+ const consequence = node.childForFieldName('consequence');
536
+ const alternative = node.childForFieldName('alternative');
537
+ if (cond)
538
+ this.walkValue(cond, acc);
539
+ if (consequence)
540
+ this.conditional(() => this.walkValue(consequence, acc));
541
+ if (alternative)
542
+ this.conditional(() => this.walkValue(alternative, acc));
543
+ return;
544
+ }
545
+ case 'class_declaration': {
546
+ // The class NAME is a def (prescan declared the binding) — without
547
+ // this case the default walk would record it as a bogus USE in plain
548
+ // JS (the name is an `identifier` there; in TS it's a type_identifier
549
+ // and would be silently skipped, losing the def either way). The body
550
+ // walk picks up field-initializer uses; methods are opaque nested fns.
551
+ const name = node.childForFieldName('name');
552
+ if (name)
553
+ this.def(name, acc);
554
+ const body = node.childForFieldName('body');
555
+ if (body)
556
+ this.walkValue(body, acc);
557
+ return;
558
+ }
559
+ case 'class': {
560
+ // Class EXPRESSION: its name (if any) binds only inside the class —
561
+ // not a def in the enclosing function. Walk only the body.
562
+ const body = node.childForFieldName('body');
563
+ if (body)
564
+ this.walkValue(body, acc);
565
+ return;
566
+ }
567
+ case 'call_expression':
568
+ // #2083 M3 U1: explicit case (previously default-descended) — same
569
+ // uses, plus a taint-site record. MUST keep defs/uses byte-identical.
570
+ this.visitCall(node, acc, 'call');
571
+ return;
572
+ case 'new_expression':
573
+ this.visitCall(node, acc, 'new');
574
+ return;
575
+ case 'member_expression':
576
+ case 'subscript_expression':
577
+ // #2083 M3 U1: value-position member chain — same uses as the old
578
+ // default descent (root identifier + dynamic subscript indices), plus
579
+ // a member-read site for the innermost identifier-rooted access.
580
+ this.walkChain(node, acc, false);
581
+ return;
582
+ case 'sequence_expression': {
583
+ // Comma operator: only the LAST operand's value flows. Earlier operands
584
+ // are evaluated for side effects — record their uses but suppress
585
+ // occurrence fan-out so `exec((log(x), 'safe'))` does not taint exec's
586
+ // arg 0 with `x` (review fix). Defs/uses stay byte-identical to the old
587
+ // default descent; only the sites layer narrows.
588
+ const operands = [];
589
+ for (let i = 0; i < node.namedChildCount; i++) {
590
+ const c = node.namedChild(i);
591
+ if (c)
592
+ operands.push(c);
593
+ }
594
+ const last = operands.length - 1;
595
+ operands.forEach((op, i) => {
596
+ if (i === last)
597
+ this.walkValue(op, acc);
598
+ else
599
+ acc.suppressOccurrences(() => this.walkValue(op, acc));
600
+ });
601
+ return;
602
+ }
603
+ default:
604
+ for (let i = 0; i < node.namedChildCount; i++) {
605
+ const c = node.namedChild(i);
606
+ if (c)
607
+ this.walkValue(c, acc);
608
+ }
609
+ }
610
+ }
611
+ /** Assignment-target walk: identifiers bind; member/subscript targets are uses. */
612
+ walkDefPattern(node, acc) {
613
+ switch (node.type) {
614
+ case 'identifier':
615
+ case 'shorthand_property_identifier_pattern':
616
+ this.def(node, acc);
617
+ return;
618
+ case 'rest_pattern':
619
+ case 'object_pattern':
620
+ case 'array_pattern':
621
+ for (let i = 0; i < node.namedChildCount; i++) {
622
+ const c = node.namedChild(i);
623
+ if (c)
624
+ this.walkDefPattern(c, acc);
625
+ }
626
+ return;
627
+ case 'pair_pattern': {
628
+ const key = node.childForFieldName('key');
629
+ const value = node.childForFieldName('value');
630
+ if (key?.type === 'computed_property_name')
631
+ this.walkValue(key, acc);
632
+ if (value)
633
+ this.walkDefPattern(value, acc);
634
+ return;
635
+ }
636
+ case 'assignment_pattern':
637
+ case 'object_assignment_pattern': {
638
+ const left = node.childForFieldName('left');
639
+ const right = node.childForFieldName('right');
640
+ if (left)
641
+ this.walkDefPattern(left, acc);
642
+ if (right)
643
+ this.walkValue(right, acc);
644
+ return;
645
+ }
646
+ case 'member_expression':
647
+ case 'subscript_expression':
648
+ // Property/element write — NOT a scalar def (KTD4); its identifiers
649
+ // (object, computed key) are uses. WRITE position (#2083 M3 U1): the
650
+ // written access itself is not a value read — no member-read site for
651
+ // it (`obj.p = q` records nothing; `req.body.x = v`'s mid-chain LOAD
652
+ // of `req.body` still does).
653
+ this.walkChain(node, acc, true);
654
+ return;
655
+ default:
656
+ for (let i = 0; i < node.namedChildCount; i++) {
657
+ const c = node.namedChild(i);
658
+ if (c && !TYPE_CONTEXT_TYPES.has(c.type))
659
+ this.walkDefPattern(c, acc);
660
+ }
661
+ }
662
+ }
663
+ // ── taint-site harvest (#2083 M3 U1) ────────────────────────────────────
664
+ /** Strip value-transparent wrappers (`(x)`, `x!`, `x as T`, `await x`). */
665
+ unwrapValueWrappers(node) {
666
+ let n = node;
667
+ while (VALUE_WRAPPER_TYPES.has(n.type)) {
668
+ const inner = n.namedChild(0);
669
+ if (!inner)
670
+ break;
671
+ n = inner;
672
+ }
673
+ return n;
674
+ }
675
+ /**
676
+ * When `value`'s root (after unwrapping) is a call/new node, remember that
677
+ * its site should carry `resultDefs: defs` — consumed by {@link visitCall}
678
+ * once the value walk reaches the node.
679
+ */
680
+ registerResultDefs(value, defs) {
681
+ if (defs.length === 0)
682
+ return;
683
+ const root = this.unwrapValueWrappers(value);
684
+ if (root.type === 'call_expression' || root.type === 'new_expression') {
685
+ this.resultDefTargets.set(root.id, [...defs]);
686
+ }
687
+ }
688
+ /**
689
+ * Explicit call/new handler: records a call site (callee path, receiver,
690
+ * per-arg occurrence entries, spread/template markers, require literal,
691
+ * result defs) while reproducing EXACTLY the uses the old default descent
692
+ * recorded — callee chain root + dynamic subscript indices + arguments.
693
+ */
694
+ visitCall(node, acc, kind) {
695
+ const calleeNode = node.childForFieldName(kind === 'new' ? 'constructor' : 'function');
696
+ const argsNode = node.childForFieldName('arguments');
697
+ // `node` IS the call_expression/new_expression — the SAME node the
698
+ // scope-extractor anchors `@reference.call.*` (its `atRange`) on (KTD7).
699
+ const siteIdx = acc.openCallSite(kind, [node.startPosition.row + 1, node.startPosition.column]);
700
+ acc.pushFrame(siteIdx);
701
+ let calleePath;
702
+ if (calleeNode) {
703
+ const callee = this.unwrapValueWrappers(calleeNode);
704
+ if (callee.type === 'identifier') {
705
+ // The callee NAME is a statement-level use but NOT a value occurrence
706
+ // flowing into any enclosing argument — `exec(escape(x))` must not
707
+ // put the `escape` binding itself into exec's arg 0 (only x, tagged
708
+ // via the escape site). Receiver-chain roots DO fan out (KTD5 TITO).
709
+ acc.addUseWithoutOccurrence(this.resolve(callee));
710
+ calleePath = callee.text;
711
+ }
712
+ else if (callee.type === 'member_expression' || callee.type === 'subscript_expression') {
713
+ // skipFinalRead: the final access IS the callee, carried by the
714
+ // dotted path — recording it as a member read would double-count.
715
+ // Mid-chain reads (`req.body` inside `req.body.toString()`) ARE
716
+ // recorded (plan KTD2).
717
+ const chain = this.walkChain(callee, acc, true);
718
+ calleePath = chain.path;
719
+ if (chain.rootIdx !== undefined)
720
+ acc.setSiteReceiver(siteIdx, chain.rootIdx);
721
+ }
722
+ else {
723
+ // Call-rooted chains, IIFEs, function expressions — no dotted path;
724
+ // the walk still records uses and nested sites.
725
+ this.walkValue(callee, acc);
726
+ }
727
+ if (calleePath !== undefined)
728
+ acc.setSiteCallee(siteIdx, calleePath);
729
+ }
730
+ const resultDefs = this.resultDefTargets.get(node.id);
731
+ if (resultDefs !== undefined)
732
+ acc.setSiteResultDefs(siteIdx, resultDefs);
733
+ if (argsNode?.type === 'template_string') {
734
+ // Tagged template (`sql\`…${id}\``): the `arguments` field is a
735
+ // template_string, not an arguments node — substitution occurrences
736
+ // aggregate at position 0 and the site is marked non-positional.
737
+ acc.setSiteTemplate(siteIdx);
738
+ acc.setFrameArg(0);
739
+ this.walkValue(argsNode, acc);
740
+ }
741
+ else if (argsNode) {
742
+ let pos = 0;
743
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
744
+ const arg = argsNode.namedChild(i);
745
+ if (!arg || arg.type === 'comment')
746
+ continue;
747
+ acc.setFrameArg(pos);
748
+ if (arg.type === 'spread_element') {
749
+ acc.setSiteSpread(siteIdx, pos);
750
+ const inner = arg.namedChild(0);
751
+ if (inner)
752
+ this.walkValue(inner, acc);
753
+ }
754
+ else {
755
+ if (kind === 'call' && pos === 0 && calleePath === 'require' && arg.type === 'string') {
756
+ // CommonJS `require('lit')` — record the literal so the matcher
757
+ // resolves require'd aliases like ESM imports (plan KTD7).
758
+ acc.setSiteRequireArg(siteIdx, stringLiteralText(arg));
759
+ }
760
+ this.walkValue(arg, acc);
761
+ }
762
+ pos++;
763
+ }
764
+ }
765
+ acc.popFrame();
766
+ }
767
+ /**
768
+ * Member/subscript chain walk shared by value position, write position, and
769
+ * callee position. Use-recording is identical to the old default descent
770
+ * (chain-root identifier once, dynamic subscript index expressions, full
771
+ * walk of non-identifier roots) — NO double-recording. Member-read sites:
772
+ * at most ONE per chain — the INNERMOST access — and only when the chain
773
+ * root is an identifier and the access's key is static (`.prop` or a
774
+ * string-literal subscript); `skipFinalRead` suppresses it when that access
775
+ * is the final one (callee / write target). Optional chaining (`?.`) never
776
+ * appears in the output (field-based traversal normalizes it); dynamic
777
+ * computed keys record nothing (documented KTD10 FN).
778
+ */
779
+ walkChain(node, acc, skipFinalRead) {
780
+ // Collect accesses outer→inner (unshift), then resolve the root.
781
+ const accesses = [];
782
+ let cur = this.unwrapValueWrappers(node);
783
+ for (;;) {
784
+ if (cur.type === 'member_expression') {
785
+ const prop = cur.childForFieldName('property');
786
+ accesses.unshift({ prop: prop?.text });
787
+ const obj = cur.childForFieldName('object');
788
+ if (!obj)
789
+ break;
790
+ cur = this.unwrapValueWrappers(obj);
791
+ }
792
+ else if (cur.type === 'subscript_expression') {
793
+ const index = cur.childForFieldName('index');
794
+ if (index?.type === 'string') {
795
+ accesses.unshift({ prop: stringLiteralText(index) });
796
+ }
797
+ else {
798
+ accesses.unshift({ dynamicIndex: index ?? undefined });
799
+ }
800
+ const obj = cur.childForFieldName('object');
801
+ if (!obj)
802
+ break;
803
+ cur = this.unwrapValueWrappers(obj);
804
+ }
805
+ else {
806
+ break;
807
+ }
808
+ }
809
+ let rootIdx;
810
+ let rootSegment;
811
+ if (cur.type === 'identifier') {
812
+ rootIdx = this.resolve(cur);
813
+ acc.addUse(rootIdx);
814
+ rootSegment = cur.text;
815
+ }
816
+ else if (cur.type === 'this' || cur.type === 'super') {
817
+ rootSegment = cur.text; // path segment only — `this`/`super` never bind
818
+ }
819
+ else {
820
+ this.walkValue(cur, acc); // call-rooted etc. — uses + nested sites
821
+ }
822
+ // Dynamic subscript index expressions are real value reads (old default
823
+ // descent walked them) — inner→outer matches the old recording order.
824
+ for (const a of accesses) {
825
+ if (a.dynamicIndex)
826
+ this.walkValue(a.dynamicIndex, acc);
827
+ }
828
+ const innermost = accesses[0];
829
+ if (rootIdx !== undefined &&
830
+ innermost?.prop !== undefined &&
831
+ !(skipFinalRead && accesses.length === 1)) {
832
+ acc.addMemberRead(rootIdx, innermost.prop);
833
+ }
834
+ const path = rootSegment !== undefined && accesses.every((a) => a.prop !== undefined)
835
+ ? [rootSegment, ...accesses.map((a) => a.prop)].join('.')
836
+ : undefined;
837
+ return { path, rootIdx };
838
+ }
839
+ }
840
+ /** Ordered, deduplicating def/use collector for one statement record. */
841
+ class FactAccumulator {
842
+ line;
843
+ defs = [];
844
+ uses = [];
845
+ mayDefs = [];
846
+ defSeen = new Set();
847
+ useSeen = new Set();
848
+ mayDefSeen = new Set();
849
+ /** Taint sites recorded for this statement (#2083 M3 U1). */
850
+ sites = [];
851
+ /** Composite (object|property|parent) keys of recorded member-read sites, so
852
+ * dedup is O(1) instead of a rescan of `sites` per read. */
853
+ memberReadKeys = new Set();
854
+ /** Stack of open call/new sites — the occurrence fan-out targets. */
855
+ frames = [];
856
+ constructor(line) {
857
+ this.line = line;
858
+ }
859
+ addDef(idx) {
860
+ if (this.defSeen.has(idx))
861
+ return;
862
+ this.defSeen.add(idx);
863
+ this.defs.push(idx);
864
+ }
865
+ /** A def that may not execute (conditional context) — gen without kill. */
866
+ addMayDef(idx) {
867
+ if (this.mayDefSeen.has(idx))
868
+ return;
869
+ this.mayDefSeen.add(idx);
870
+ this.mayDefs.push(idx);
871
+ }
872
+ addUse(idx) {
873
+ // Occurrence fan-out happens BEFORE the statement-level dedup: `exec(x, x)`
874
+ // records x at BOTH arg positions even though `uses` lists it once.
875
+ this.recordOccurrence(idx);
876
+ this.addUseWithoutOccurrence(idx);
877
+ }
878
+ /**
879
+ * Statement-level use that is NOT a value occurrence in any open site
880
+ * argument — bare callee names only (#2083 M3 U1, see visitCall).
881
+ */
882
+ addUseWithoutOccurrence(idx) {
883
+ if (this.useSeen.has(idx))
884
+ return;
885
+ this.useSeen.add(idx);
886
+ this.uses.push(idx);
887
+ }
888
+ defCount() {
889
+ return this.defs.length + this.mayDefs.length;
890
+ }
891
+ useCount() {
892
+ return this.uses.length;
893
+ }
894
+ // ── site machinery (#2083 M3 U1) ─────────────────────────────────────────
895
+ /** `[defs.length, mayDefs.length]` marker for {@link defsSince}. */
896
+ defSnapshot() {
897
+ return [this.defs.length, this.mayDefs.length];
898
+ }
899
+ /** Binding indices def'd (must- OR may-) since the snapshot was taken. */
900
+ defsSince(snap) {
901
+ return [...this.defs.slice(snap[0]), ...this.mayDefs.slice(snap[1])];
902
+ }
903
+ /**
904
+ * Open a call/new site; parent = innermost enclosing argument position. `at`
905
+ * is the call/new node's anchor position `[line (1-based), col (0-based)]` —
906
+ * the SAME position the CALLS-edge resolution keys on (KTD7; see
907
+ * {@link SiteRecord.at}).
908
+ */
909
+ openCallSite(kind, at) {
910
+ const site = { kind };
911
+ const parent = this.innermostArgPosition();
912
+ if (parent)
913
+ site.parent = parent;
914
+ if (at)
915
+ site.at = [at[0], at[1]];
916
+ this.sites.push(site);
917
+ return this.sites.length - 1;
918
+ }
919
+ pushFrame(siteIdx) {
920
+ this.frames.push({ siteIdx, argIdx: -1 });
921
+ }
922
+ popFrame() {
923
+ this.frames.pop();
924
+ }
925
+ /** Set the argument position the top frame is currently walking. */
926
+ setFrameArg(argIdx) {
927
+ const top = this.frames[this.frames.length - 1];
928
+ if (top)
929
+ top.argIdx = argIdx;
930
+ }
931
+ /**
932
+ * Run `fn` with all open arg frames temporarily detached (argIdx = -1), so
933
+ * identifier reads inside still record USES but do NOT fan occurrences into
934
+ * the enclosing sink-argument position. Used for the non-value operands of a
935
+ * sequence (comma) expression — only the final operand's value flows.
936
+ */
937
+ suppressOccurrences(fn) {
938
+ const saved = this.frames.map((f) => f.argIdx);
939
+ for (const f of this.frames)
940
+ f.argIdx = -1;
941
+ try {
942
+ fn();
943
+ }
944
+ finally {
945
+ this.frames.forEach((f, i) => {
946
+ f.argIdx = saved[i];
947
+ });
948
+ }
949
+ }
950
+ setSiteCallee(siteIdx, callee) {
951
+ this.sites[siteIdx].callee = callee;
952
+ }
953
+ setSiteReceiver(siteIdx, receiver) {
954
+ this.sites[siteIdx].receiver = receiver;
955
+ }
956
+ setSiteResultDefs(siteIdx, resultDefs) {
957
+ this.sites[siteIdx].resultDefs = [...resultDefs];
958
+ }
959
+ setSiteSpread(siteIdx, firstSpreadArg) {
960
+ const site = this.sites[siteIdx];
961
+ if (site.spread === undefined)
962
+ site.spread = firstSpreadArg;
963
+ }
964
+ setSiteTemplate(siteIdx) {
965
+ this.sites[siteIdx].template = true;
966
+ }
967
+ setSiteRequireArg(siteIdx, literal) {
968
+ this.sites[siteIdx].requireArg = literal;
969
+ }
970
+ /**
971
+ * Record a value-position member read. Exact duplicates within the
972
+ * statement (same object/property/parent position) dedup; reads at
973
+ * DIFFERENT argument positions stay distinct (`exec(req.body, req.body)`
974
+ * is two occurrences — KTD6 finding identity needs both).
975
+ */
976
+ addMemberRead(object, property) {
977
+ const parent = this.innermostArgPosition();
978
+ const dedupKey = `${object}|${property}|${parent ? `${parent[0]}:${parent[1]}` : 'top'}`;
979
+ if (this.memberReadKeys.has(dedupKey))
980
+ return;
981
+ this.memberReadKeys.add(dedupKey);
982
+ const site = { kind: 'member-read' };
983
+ if (parent)
984
+ site.parent = parent;
985
+ site.object = object;
986
+ site.property = property;
987
+ this.sites.push(site);
988
+ }
989
+ innermostArgPosition() {
990
+ for (let i = this.frames.length - 1; i >= 0; i--) {
991
+ const f = this.frames[i];
992
+ if (f.argIdx >= 0)
993
+ return [f.siteIdx, f.argIdx];
994
+ }
995
+ return undefined;
996
+ }
997
+ /**
998
+ * Fan a binding occurrence out to every arg-active open frame. The entry is
999
+ * via-tagged with the site of the IMMEDIATELY nested frame when one exists:
1000
+ * `exec(escape(x))` puts a plain `x` in escape's arg 0 and `[x, escapeIdx]`
1001
+ * in exec's arg 0 — the KTD4a interposition substrate.
1002
+ */
1003
+ recordOccurrence(idx) {
1004
+ for (let i = this.frames.length - 1; i >= 0; i--) {
1005
+ const f = this.frames[i];
1006
+ if (f.argIdx < 0)
1007
+ continue;
1008
+ const via = i + 1 < this.frames.length ? this.frames[i + 1].siteIdx : undefined;
1009
+ this.pushArgEntry(f.siteIdx, f.argIdx, idx, via);
1010
+ }
1011
+ }
1012
+ pushArgEntry(siteIdx, argIdx, bindingIdx, via) {
1013
+ const site = this.sites[siteIdx];
1014
+ const args = (site.args ??= []);
1015
+ while (args.length <= argIdx)
1016
+ args.push([]);
1017
+ const list = args[argIdx];
1018
+ // Dedup exact (binding, via) pairs per position — `f(x + x)` is one entry;
1019
+ // `f(x + g(x))` keeps the plain AND the via-tagged entry (distinct paths).
1020
+ for (const e of list) {
1021
+ const match = typeof e === 'number'
1022
+ ? via === undefined && e === bindingIdx
1023
+ : via !== undefined && e[0] === bindingIdx && e[1] === via;
1024
+ if (match)
1025
+ return;
1026
+ }
1027
+ list.push(via === undefined ? bindingIdx : [bindingIdx, via]);
1028
+ }
1029
+ finish() {
1030
+ return {
1031
+ line: this.line,
1032
+ defs: this.defs,
1033
+ uses: this.uses,
1034
+ // Optional field stays absent when empty — keeps the serialized
1035
+ // side-channel payload lean (most statements have no may-defs).
1036
+ ...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
1037
+ // Sites likewise omit-when-empty (#2083 M3 U1): flag-off runs never
1038
+ // harvest, and most fact-bearing statements carry no calls.
1039
+ ...(this.sites.length > 0 ? { sites: this.sites.map(finalizeSite) } : {}),
1040
+ };
1041
+ }
1042
+ }
1043
+ /** Trim trailing empty arg positions; drop `args` entirely when all-empty. */
1044
+ const finalizeSite = (site) => {
1045
+ const args = site.args;
1046
+ if (args !== undefined) {
1047
+ let end = args.length;
1048
+ while (end > 0 && args[end - 1].length === 0)
1049
+ end--;
1050
+ if (end === 0)
1051
+ delete site.args;
1052
+ else if (end < args.length)
1053
+ site.args = args.slice(0, end);
1054
+ }
1055
+ return site;
1056
+ };