@backbencher/brain 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/EMBEDDINGS.md +355 -0
  3. package/LICENSE +21 -0
  4. package/README.md +415 -2
  5. package/bin/bb +13 -0
  6. package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
  7. package/dist/brain-index-health-Q5APTJQL.js +17 -0
  8. package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-22V37YTK.js +1758 -0
  11. package/dist/chunk-2BSKD7O3.js +40 -0
  12. package/dist/chunk-2DE27DVC.js +1314 -0
  13. package/dist/chunk-2RRBMNBQ.js +322 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3ZTOHHSW.js +2104 -0
  17. package/dist/chunk-4H22TLOT.js +543 -0
  18. package/dist/chunk-5BVAH26D.js +340 -0
  19. package/dist/chunk-62DHLYW6.js +3427 -0
  20. package/dist/chunk-7AN7U5IZ.js +371 -0
  21. package/dist/chunk-7DRPNVIN.js +782 -0
  22. package/dist/chunk-7MBYT777.js +174 -0
  23. package/dist/chunk-AHDWGM7Q.js +236 -0
  24. package/dist/chunk-B5GEAAYF.js +818 -0
  25. package/dist/chunk-BEIZZ73B.js +1479 -0
  26. package/dist/chunk-BQ3OVQ3P.js +125 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DWKXZ6IF.js +336 -0
  30. package/dist/chunk-DYM4FQEE.js +90 -0
  31. package/dist/chunk-ENW4BTBA.js +930 -0
  32. package/dist/chunk-EWMWK7WN.js +75 -0
  33. package/dist/chunk-EY5ZZUUA.js +626 -0
  34. package/dist/chunk-GBI7V7KY.js +69 -0
  35. package/dist/chunk-GEFFO6O2.js +152 -0
  36. package/dist/chunk-HJEWO5EV.js +247 -0
  37. package/dist/chunk-HQSPJDHI.js +613 -0
  38. package/dist/chunk-KAET47ZL.js +125 -0
  39. package/dist/chunk-KCDECRJY.js +440 -0
  40. package/dist/chunk-L5TDYKYK.js +76 -0
  41. package/dist/chunk-MLKGABMK.js +9 -0
  42. package/dist/chunk-NO3U3FJQ.js +201 -0
  43. package/dist/chunk-OWISBAOF.js +61 -0
  44. package/dist/chunk-QKTRECYS.js +1119 -0
  45. package/dist/chunk-QLK6ED4Q.js +235 -0
  46. package/dist/chunk-R2GW4F6R.js +19 -0
  47. package/dist/chunk-RCMTFSEF.js +87 -0
  48. package/dist/chunk-RM3PY6TG.js +376 -0
  49. package/dist/chunk-ROMM6KMC.js +433 -0
  50. package/dist/chunk-SGGCSHAX.js +12050 -0
  51. package/dist/chunk-SK6YVHFZ.js +211 -0
  52. package/dist/chunk-SM7RFQRO.js +214 -0
  53. package/dist/chunk-UGR4QIGS.js +67 -0
  54. package/dist/chunk-UQNQZXHY.js +711 -0
  55. package/dist/chunk-UUNHHX4M.js +13 -0
  56. package/dist/chunk-VL52PL2T.js +71 -0
  57. package/dist/chunk-WIG36E67.js +196 -0
  58. package/dist/chunk-XHK3WWGB.js +7436 -0
  59. package/dist/chunk-XTQM2IP3.js +2863 -0
  60. package/dist/chunk-ZE2LG76D.js +1296 -0
  61. package/dist/chunk-ZETTGPAW.js +6950 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-7XIP555Q.js +593 -0
  65. package/dist/database-76RP44JG.js +9 -0
  66. package/dist/doctor-ANGETYMN.js +395 -0
  67. package/dist/edit-engine-CG72LDZD.js +51 -0
  68. package/dist/engine-BKEOGN7N.js +11 -0
  69. package/dist/engine-VIW6NSKO.js +18 -0
  70. package/dist/fingerprint-OBLIMZLP.js +33 -0
  71. package/dist/framework-intelligence-3LORTPOX.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-HBSQNPTB.js +21 -0
  74. package/dist/graph_request-Q6NKB6N2.js +350 -0
  75. package/dist/impact_request-LYP672WE.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-7OVGYSGW.js +34 -0
  78. package/dist/index.js +159 -0
  79. package/dist/instance-FO6QZRLH.js +19 -0
  80. package/dist/language-detection-DFCAU5DZ.js +11 -0
  81. package/dist/lsp/server.js +4301 -0
  82. package/dist/mcp/server.js +132 -0
  83. package/dist/onboard-DIQZV2NW.js +161 -0
  84. package/dist/override-companion-pass-XR7MHTP7.js +117 -0
  85. package/dist/parallel-indexer-FK2XKZXY.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-6XPENVAA.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-ZJ3DVUGO.js +355 -0
  90. package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
  91. package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
  92. package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
  93. package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
  94. package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
  95. package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
  96. package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
  97. package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
  98. package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
  99. package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
  100. package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
  101. package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
  102. package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
  103. package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
  104. package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
  105. package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
  106. package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
  107. package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
  108. package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
  109. package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
  110. package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
  111. package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
  112. package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
  113. package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
  114. package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
  115. package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
  116. package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
  117. package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
  118. package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
  119. package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
  120. package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
  121. package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
  122. package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
  123. package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
  124. package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
  125. package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
  126. package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
  127. package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
  128. package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
  129. package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
  130. package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
  131. package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
  132. package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
  133. package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
  134. package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
  135. package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
  136. package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
  137. package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
  138. package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
  139. package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
  140. package/dist/regex-search-NJKHMZNO.js +239 -0
  141. package/dist/rules-ACHPB7QU.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-NT5ZHYNE.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-GZJIM2J2.js +16 -0
  146. package/dist/taint-KXUHHPIE.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-OJDC35DI.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-Q57YMMBX.js +32 -0
  151. package/dist/type-inference-QMT2RMLV.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-ABP2TUL2.js +9 -0
  154. package/dist/wasm-NMWO3WXY.js +16 -0
  155. package/docs/EMBEDDINGS.md +132 -0
  156. package/docs/README.md +178 -0
  157. package/docs/data/m10-baseline.json +602 -0
  158. package/docs/data/m10-benchmark-baseline.json +57 -0
  159. package/docs/data/m10-benchmark-last-run.json +127 -0
  160. package/docs/data/m10-final-audit.json +215 -0
  161. package/docs/data/m12-final-audit.json +138 -0
  162. package/docs/data/m12-surface-matrix.json +50 -0
  163. package/docs/data/public-tools.json +1902 -0
  164. package/docs/indexing/DEAD-ASSETS.md +88 -0
  165. package/docs/indexing/INDEXER-REVIEW.md +634 -0
  166. package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
  167. package/docs/indexing/README.md +427 -0
  168. package/docs/indexing/discovery-matrix.md +41 -0
  169. package/docs/indexing/embeddings.md +325 -0
  170. package/docs/indexing/environment-variables.md +77 -0
  171. package/docs/indexing/performance.md +323 -0
  172. package/docs/indexing/resume.md +345 -0
  173. package/docs/indexing/troubleshooting.md +391 -0
  174. package/docs/man/bb.1 +190 -0
  175. package/docs/tools/bb_analyze.md +147 -0
  176. package/docs/tools/bb_annotate.md +110 -0
  177. package/docs/tools/bb_annotations.md +106 -0
  178. package/docs/tools/bb_ask.md +101 -0
  179. package/docs/tools/bb_cross_language.md +103 -0
  180. package/docs/tools/bb_doc.md +169 -0
  181. package/docs/tools/bb_edit.md +185 -0
  182. package/docs/tools/bb_graph.md +169 -0
  183. package/docs/tools/bb_health.md +156 -0
  184. package/docs/tools/bb_impact.md +117 -0
  185. package/docs/tools/bb_index.md +108 -0
  186. package/docs/tools/bb_migrate.md +135 -0
  187. package/docs/tools/bb_patterns.md +140 -0
  188. package/docs/tools/bb_search.md +150 -0
  189. package/docs/tools/bb_security.md +100 -0
  190. package/docs/tools/bb_visualize.md +184 -0
  191. package/grammars/README.md +57 -0
  192. package/grammars/THIRD-PARTY-NOTICES.md +50 -0
  193. package/grammars/tree-sitter-c/LICENSE +21 -0
  194. package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
  195. package/grammars/tree-sitter-cpp/LICENSE +21 -0
  196. package/grammars/tree-sitter-csharp/LICENSE +21 -0
  197. package/grammars/tree-sitter-css/LICENSE +21 -0
  198. package/grammars/tree-sitter-css/README.md +21 -0
  199. package/grammars/tree-sitter-dart/LICENSE +20 -0
  200. package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
  201. package/grammars/tree-sitter-dart-upstream/README.md +18 -0
  202. package/grammars/tree-sitter-elixir/LICENSE +176 -0
  203. package/grammars/tree-sitter-elixir/README.md +11 -0
  204. package/grammars/tree-sitter-fsharp/LICENSE +21 -0
  205. package/grammars/tree-sitter-fsharp/README.md +45 -0
  206. package/grammars/tree-sitter-graphql/LICENSE +21 -0
  207. package/grammars/tree-sitter-graphql/README.md +106 -0
  208. package/grammars/tree-sitter-groovy/LICENSE +21 -0
  209. package/grammars/tree-sitter-groovy/README.md +4 -0
  210. package/grammars/tree-sitter-haskell/LICENSE +21 -0
  211. package/grammars/tree-sitter-haskell/README.md +394 -0
  212. package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
  213. package/grammars/tree-sitter-hcl/LICENSE +201 -0
  214. package/grammars/tree-sitter-hcl/README.md +39 -0
  215. package/grammars/tree-sitter-html/LICENSE +21 -0
  216. package/grammars/tree-sitter-html/README.md +21 -0
  217. package/grammars/tree-sitter-julia/LICENSE +21 -0
  218. package/grammars/tree-sitter-julia/README.md +24 -0
  219. package/grammars/tree-sitter-kotlin/LICENSE +9 -0
  220. package/grammars/tree-sitter-lua/LICENSE.md +21 -0
  221. package/grammars/tree-sitter-lua/README.md +29 -0
  222. package/grammars/tree-sitter-make/LICENSE +21 -0
  223. package/grammars/tree-sitter-matlab/LICENSE +19 -0
  224. package/grammars/tree-sitter-matlab/README.md +99 -0
  225. package/grammars/tree-sitter-nim/LICENSE +373 -0
  226. package/grammars/tree-sitter-nim/README.md +46 -0
  227. package/grammars/tree-sitter-objc/LICENSE +21 -0
  228. package/grammars/tree-sitter-objc/README.md +20 -0
  229. package/grammars/tree-sitter-perl/LICENSE +7 -0
  230. package/grammars/tree-sitter-perl/README.md +100 -0
  231. package/grammars/tree-sitter-powershell/LICENSE +21 -0
  232. package/grammars/tree-sitter-powershell/README.md +7 -0
  233. package/grammars/tree-sitter-proto/LICENSE +21 -0
  234. package/grammars/tree-sitter-proto/README.md +105 -0
  235. package/grammars/tree-sitter-r/LICENSE +9 -0
  236. package/grammars/tree-sitter-r/README.md +66 -0
  237. package/grammars/tree-sitter-ruby/LICENSE +22 -0
  238. package/grammars/tree-sitter-scss/LICENSE +21 -0
  239. package/grammars/tree-sitter-scss/README.md +17 -0
  240. package/grammars/tree-sitter-solidity/LICENSE +7 -0
  241. package/grammars/tree-sitter-sql/LICENSE +21 -0
  242. package/grammars/tree-sitter-typescript/LICENSE +21 -0
  243. package/grammars/tree-sitter-vba/LICENSE +21 -0
  244. package/grammars/tree-sitter-vba/README.md +738 -0
  245. package/grammars/tree-sitter-vbnet/LICENSE +21 -0
  246. package/grammars/tree-sitter-vbnet/README.md +322 -0
  247. package/grammars/tree-sitter-xml/LICENSE +19 -0
  248. package/grammars/tree-sitter-yaml/LICENSE +20 -0
  249. package/grammars/tree-sitter-yaml/README.md +21 -0
  250. package/package.json +184 -4
  251. package/wasm/dart.wasm +0 -0
  252. package/wasm/tree-sitter-bash.wasm +0 -0
  253. package/wasm/tree-sitter-c.wasm +0 -0
  254. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  255. package/wasm/tree-sitter-clean.wasm +0 -0
  256. package/wasm/tree-sitter-clojure.wasm +0 -0
  257. package/wasm/tree-sitter-core_schema.wasm +0 -0
  258. package/wasm/tree-sitter-cpp.wasm +0 -0
  259. package/wasm/tree-sitter-css.wasm +0 -0
  260. package/wasm/tree-sitter-dart.wasm +0 -0
  261. package/wasm/tree-sitter-elisp.wasm +0 -0
  262. package/wasm/tree-sitter-elixir.wasm +0 -0
  263. package/wasm/tree-sitter-elm.wasm +0 -0
  264. package/wasm/tree-sitter-embedded_template.wasm +0 -0
  265. package/wasm/tree-sitter-erlang.wasm +0 -0
  266. package/wasm/tree-sitter-fsharp.wasm +0 -0
  267. package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
  268. package/wasm/tree-sitter-go.wasm +0 -0
  269. package/wasm/tree-sitter-graphql.wasm +0 -0
  270. package/wasm/tree-sitter-groovy.wasm +0 -0
  271. package/wasm/tree-sitter-haskell.wasm +0 -0
  272. package/wasm/tree-sitter-hcl.wasm +0 -0
  273. package/wasm/tree-sitter-html.wasm +0 -0
  274. package/wasm/tree-sitter-java.wasm +0 -0
  275. package/wasm/tree-sitter-javascript.wasm +0 -0
  276. package/wasm/tree-sitter-json.wasm +0 -0
  277. package/wasm/tree-sitter-json_schema.wasm +0 -0
  278. package/wasm/tree-sitter-julia.wasm +0 -0
  279. package/wasm/tree-sitter-kotlin.wasm +0 -0
  280. package/wasm/tree-sitter-lua.wasm +0 -0
  281. package/wasm/tree-sitter-make.wasm +0 -0
  282. package/wasm/tree-sitter-markdown.wasm +0 -0
  283. package/wasm/tree-sitter-markdown_inline.wasm +0 -0
  284. package/wasm/tree-sitter-matlab.wasm +0 -0
  285. package/wasm/tree-sitter-nim.wasm +0 -0
  286. package/wasm/tree-sitter-objc.wasm +0 -0
  287. package/wasm/tree-sitter-ocaml.wasm +0 -0
  288. package/wasm/tree-sitter-perl.wasm +0 -0
  289. package/wasm/tree-sitter-php.wasm +0 -0
  290. package/wasm/tree-sitter-php_only.wasm +0 -0
  291. package/wasm/tree-sitter-powershell.wasm +0 -0
  292. package/wasm/tree-sitter-protobuf.wasm +0 -0
  293. package/wasm/tree-sitter-python.wasm +0 -0
  294. package/wasm/tree-sitter-ql.wasm +0 -0
  295. package/wasm/tree-sitter-r.wasm +0 -0
  296. package/wasm/tree-sitter-rescript.wasm +0 -0
  297. package/wasm/tree-sitter-ruby.wasm +0 -0
  298. package/wasm/tree-sitter-rust.wasm +0 -0
  299. package/wasm/tree-sitter-scala.wasm +0 -0
  300. package/wasm/tree-sitter-scss.wasm +0 -0
  301. package/wasm/tree-sitter-solidity.wasm +0 -0
  302. package/wasm/tree-sitter-sql.wasm +0 -0
  303. package/wasm/tree-sitter-svelte.wasm +0 -0
  304. package/wasm/tree-sitter-swift.wasm +0 -0
  305. package/wasm/tree-sitter-systemrdl.wasm +0 -0
  306. package/wasm/tree-sitter-terraform.wasm +0 -0
  307. package/wasm/tree-sitter-tlaplus.wasm +0 -0
  308. package/wasm/tree-sitter-toml.wasm +0 -0
  309. package/wasm/tree-sitter-tsx.wasm +0 -0
  310. package/wasm/tree-sitter-typescript.wasm +0 -0
  311. package/wasm/tree-sitter-vba.wasm +0 -0
  312. package/wasm/tree-sitter-vbnet.wasm +0 -0
  313. package/wasm/tree-sitter-vue.wasm +0 -0
  314. package/wasm/tree-sitter-xml.wasm +0 -0
  315. package/wasm/tree-sitter-yaml.wasm +0 -0
  316. package/wasm/tree-sitter-zig.wasm +0 -0
  317. package/wasm/tree-sitter.wasm +0 -0
  318. package/wasm/tsx.wasm +0 -0
  319. package/wasm/typescript.wasm +0 -0
@@ -0,0 +1,2532 @@
1
+ import {
2
+ RealTypeScriptAnalyzer
3
+ } from "./chunk-XHK3WWGB.js";
4
+ import {
5
+ RealJavaScriptAnalyzer
6
+ } from "./chunk-62DHLYW6.js";
7
+ import "./chunk-R2GW4F6R.js";
8
+ import {
9
+ ASTEngine
10
+ } from "./chunk-3ZTOHHSW.js";
11
+ import "./chunk-BQ3OVQ3P.js";
12
+ import "./chunk-SK6YVHFZ.js";
13
+ import "./chunk-2BSKD7O3.js";
14
+ import "./chunk-KAET47ZL.js";
15
+ import "./chunk-KCDECRJY.js";
16
+ import "./chunk-MLKGABMK.js";
17
+
18
+ // src/ast/real-svelte-analyzer.ts
19
+ import { readdirSync, readFileSync, statSync } from "fs";
20
+ import * as path from "path";
21
+ function shiftMetadataLineMirrors(md, lineOffset) {
22
+ if (!md || lineOffset === 0) return;
23
+ for (const [k, v] of Object.entries(md)) {
24
+ if (!keyCarriesALine(k)) continue;
25
+ if (typeof v === "number") md[k] = v + lineOffset;
26
+ else if (Array.isArray(v) && v.some((x) => typeof x === "number")) {
27
+ md[k] = v.map((x) => typeof x === "number" ? x + lineOffset : x);
28
+ }
29
+ }
30
+ }
31
+ var PROJECT_PUBLISHED_PACKAGES = /* @__PURE__ */ new Map();
32
+ function projectPublishedPackages(projectRoot) {
33
+ const cached = PROJECT_PUBLISHED_PACKAGES.get(projectRoot);
34
+ if (cached) return cached;
35
+ const names = /* @__PURE__ */ new Set();
36
+ const read = (p) => {
37
+ try {
38
+ const j = JSON.parse(readFileSync(p, "utf8"));
39
+ if (typeof j?.name === "string" && j.name.trim() !== "") names.add(j.name.trim());
40
+ } catch {
41
+ }
42
+ };
43
+ read(path.join(projectRoot, "package.json"));
44
+ for (const dir of ["packages", "apps"]) {
45
+ let entries;
46
+ try {
47
+ entries = readdirSync(path.join(projectRoot, dir));
48
+ } catch {
49
+ continue;
50
+ }
51
+ for (const e of entries) read(path.join(projectRoot, dir, e, "package.json"));
52
+ }
53
+ PROJECT_PUBLISHED_PACKAGES.set(projectRoot, names);
54
+ return names;
55
+ }
56
+ var HTML_BUILTIN_TAGS = /* @__PURE__ */ new Set([
57
+ "a",
58
+ "abbr",
59
+ "address",
60
+ "area",
61
+ "article",
62
+ "aside",
63
+ "audio",
64
+ "b",
65
+ "base",
66
+ "bdi",
67
+ "bdo",
68
+ "blockquote",
69
+ "body",
70
+ "br",
71
+ "button",
72
+ "canvas",
73
+ "caption",
74
+ "cite",
75
+ "code",
76
+ "col",
77
+ "colgroup",
78
+ "data",
79
+ "datalist",
80
+ "dd",
81
+ "del",
82
+ "details",
83
+ "dfn",
84
+ "dialog",
85
+ "div",
86
+ "dl",
87
+ "dt",
88
+ "em",
89
+ "embed",
90
+ "fieldset",
91
+ "figcaption",
92
+ "figure",
93
+ "footer",
94
+ "form",
95
+ "h1",
96
+ "h2",
97
+ "h3",
98
+ "h4",
99
+ "h5",
100
+ "h6",
101
+ "head",
102
+ "header",
103
+ "hgroup",
104
+ "hr",
105
+ "html",
106
+ "i",
107
+ "iframe",
108
+ "img",
109
+ "input",
110
+ "ins",
111
+ "kbd",
112
+ "label",
113
+ "legend",
114
+ "li",
115
+ "link",
116
+ "main",
117
+ "map",
118
+ "mark",
119
+ "menu",
120
+ "meta",
121
+ "meter",
122
+ "nav",
123
+ "noscript",
124
+ "object",
125
+ "ol",
126
+ "optgroup",
127
+ "option",
128
+ "output",
129
+ "p",
130
+ "picture",
131
+ "pre",
132
+ "progress",
133
+ "q",
134
+ "rp",
135
+ "rt",
136
+ "ruby",
137
+ "s",
138
+ "samp",
139
+ "script",
140
+ "search",
141
+ "section",
142
+ "select",
143
+ "slot",
144
+ "small",
145
+ "source",
146
+ "span",
147
+ "strong",
148
+ "style",
149
+ "sub",
150
+ "summary",
151
+ "sup",
152
+ "table",
153
+ "tbody",
154
+ "td",
155
+ "template",
156
+ "textarea",
157
+ "tfoot",
158
+ "th",
159
+ "thead",
160
+ "time",
161
+ "title",
162
+ "tr",
163
+ "track",
164
+ "u",
165
+ "ul",
166
+ "var",
167
+ "video",
168
+ "wbr",
169
+ // SVG
170
+ "svg",
171
+ "path",
172
+ "g",
173
+ "rect",
174
+ "circle",
175
+ "ellipse",
176
+ "line",
177
+ "polyline",
178
+ "polygon",
179
+ "text",
180
+ "tspan",
181
+ "use",
182
+ "defs",
183
+ "linearGradient",
184
+ "radialGradient",
185
+ "stop",
186
+ "mask",
187
+ "pattern",
188
+ "marker",
189
+ "view",
190
+ "symbol",
191
+ "clipPath",
192
+ // SVG — the FILTER module and the rest of the element vocabulary (F30, 2026-10-12).
193
+ //
194
+ // Kept in LOCKSTEP with the vue lane's list: script/m14/verify-cross-language-consistency.ts
195
+ // extracts both sets and both against the oracle's ORACLE_HTML_TAGS, and fails if they
196
+ // differ, because whether a tag is markup or a component is a fact about the tag, not
197
+ // about the language it is written in. NO DOUBLE QUOTES OR CLOSING-BRACKET-PAREN PAIR in
198
+ // this comment: the parity gate slices this literal to the first such pair and treats
199
+ // every quoted string in the slice as a tag.
200
+ //
201
+ // Measured on THIS lane's corpora before assuming, and the number is zero: 0 of the
202
+ // 6,436 .svelte files on disk (svelte 4,561 + shadcn-svelte 1,878) contain a filter
203
+ // primitive or a `<filter>` element at all. So this lane inherits the vocabulary from
204
+ // the parity rule rather than from a defect anyone can see here today — the live
205
+ // measurement is the vue lane's, and this lane's obligation is that it USES the set,
206
+ // which is what svelte/svg-filter-builtin-tags pins and
207
+ // svelte/reemit-svg-filter-tags regresses.
208
+ // The defect it fixes was measured on the vue corpus (<filter>
209
+ // and every filter PRIMITIVE published `instantiates` edges to targets no file declares:
210
+ // 377 edges, 6 primitives); this lane has the same gap by construction, and the svelte
211
+ // fixture set carries the twin check through the parity gate rather than a second corpus
212
+ // measurement it has no draw for. An unknown namespace still fails toward VISIBILITY (see
213
+ // BUILTIN_NAMESPACES); an element the SVG spec DEFINES is markup.
214
+ "filter",
215
+ "feBlend",
216
+ "feColorMatrix",
217
+ "feComponentTransfer",
218
+ "feComposite",
219
+ "feConvolveMatrix",
220
+ "feDiffuseLighting",
221
+ "feDisplacementMap",
222
+ "feDistantLight",
223
+ "feDropShadow",
224
+ "feFlood",
225
+ "feFuncA",
226
+ "feFuncB",
227
+ "feFuncG",
228
+ "feFuncR",
229
+ "feGaussianBlur",
230
+ "feImage",
231
+ "feMerge",
232
+ "feMergeNode",
233
+ "feMorphology",
234
+ "feOffset",
235
+ "fePointLight",
236
+ "feSpecularLighting",
237
+ "feSpotLight",
238
+ "feTile",
239
+ "feTurbulence",
240
+ // the rest of SVG 2's element list, plus the SVG 1.1 elements browsers still parse —
241
+ // the SPEC's vocabulary, counted separately from the measured block above.
242
+ "switch",
243
+ "foreignObject",
244
+ "desc",
245
+ "metadata",
246
+ "textPath",
247
+ "tref",
248
+ "mpath",
249
+ "animate",
250
+ "animateMotion",
251
+ "animateTransform",
252
+ "set",
253
+ "discard",
254
+ "solidColor",
255
+ "mesh",
256
+ "meshgradient",
257
+ "meshpatch",
258
+ "meshrow",
259
+ "hatch",
260
+ "hatchpath",
261
+ "altGlyph",
262
+ "altGlyphDef",
263
+ "altGlyphItem",
264
+ "glyphRef",
265
+ "animateColor",
266
+ "cursor",
267
+ "color-profile",
268
+ "font",
269
+ "font-face",
270
+ "font-face-format",
271
+ "font-face-name",
272
+ "font-face-src",
273
+ "font-face-uri",
274
+ "glyph",
275
+ "hkern",
276
+ "missing-glyph",
277
+ "vkern"
278
+ ]);
279
+ var SVELTE_BUILTIN_TAGS = /* @__PURE__ */ new Set([
280
+ "slot",
281
+ "svelte:component",
282
+ "svelte:self",
283
+ "svelte:element",
284
+ "svelte:window",
285
+ "svelte:document",
286
+ "svelte:body",
287
+ "svelte:head",
288
+ "svelte:options",
289
+ "svelte:fragment",
290
+ "svelte:boundary"
291
+ ]);
292
+ var SVELTE_MEMBER_READ_DIRECTIVES = /* @__PURE__ */ new Set(["bind", "class", "style"]);
293
+ var SVELTE_INVOKED_DIRECTIVES = /* @__PURE__ */ new Set(["use", "transition", "in", "out", "animate"]);
294
+ var SVELTE_DECLARING_DIRECTIVES = /* @__PURE__ */ new Set(["let"]);
295
+ var SVELTE_RUNES = /* @__PURE__ */ new Set([
296
+ "$state",
297
+ "$state.raw",
298
+ "$state.snapshot",
299
+ "$state.eager",
300
+ "$derived",
301
+ "$derived.by",
302
+ "$effect",
303
+ "$effect.pre",
304
+ "$effect.tracking",
305
+ "$effect.root",
306
+ "$effect.untracked",
307
+ "$effect.pending",
308
+ "$props",
309
+ "$props.id",
310
+ "$props.is_safe",
311
+ "$bindable",
312
+ "$host",
313
+ "$inspect",
314
+ "$inspect.trace",
315
+ "$render",
316
+ "$props"
317
+ ]);
318
+ var NON_CALLABLE_LITERALS = /* @__PURE__ */ new Set(["null", "undefined", "true", "false", "NaN", "Infinity", "this"]);
319
+ function isIdentifier(text) {
320
+ if (text.length === 0) return false;
321
+ const first = text[0];
322
+ if (!(first >= "a" && first <= "z" || first >= "A" && first <= "Z" || first === "_" || first === "$")) return false;
323
+ for (let i = 1; i < text.length; i++) {
324
+ const c = text[i];
325
+ if (!(c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "$")) return false;
326
+ }
327
+ return true;
328
+ }
329
+ function containsWhitespace(text) {
330
+ for (let i = 0; i < text.length; i++) {
331
+ const c = text.charCodeAt(i);
332
+ if (c === 9 || c === 10 || c === 11 || c === 12 || c === 13 || c === 32 || c === 160 || c === 5760) return true;
333
+ if (c >= 8192 && c <= 8202) return true;
334
+ if (c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279) return true;
335
+ }
336
+ return false;
337
+ }
338
+ function isUriSchemeSpecifier(spec) {
339
+ const colon = spec.indexOf(":");
340
+ if (colon <= 0) return false;
341
+ for (let i = 0; i < colon; i++) {
342
+ const c = spec.charCodeAt(i);
343
+ const letter = c >= 65 && c <= 90 || c >= 97 && c <= 122;
344
+ if (i === 0) {
345
+ if (!letter) return false;
346
+ continue;
347
+ }
348
+ const digit = c >= 48 && c <= 57;
349
+ if (!letter && !digit && c !== 43 && c !== 46 && c !== 45) return false;
350
+ }
351
+ return true;
352
+ }
353
+ function isDottedIdentifierChain(text) {
354
+ let start = 0;
355
+ while (true) {
356
+ const dot = text.indexOf(".", start);
357
+ const end = dot < 0 ? text.length : dot;
358
+ if (!isIdentifier(text.slice(start, end))) return false;
359
+ if (dot < 0) return true;
360
+ start = dot + 1;
361
+ }
362
+ }
363
+ function splitAwaitHead(text) {
364
+ const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
365
+ let i = 0;
366
+ while (i < text.length) {
367
+ if (!isSpace(text[i])) {
368
+ i++;
369
+ continue;
370
+ }
371
+ const wsStart = i;
372
+ while (i < text.length && isSpace(text[i])) i++;
373
+ const kwStart = i;
374
+ let kwLen = 0;
375
+ if (text.startsWith("then", i)) kwLen = 4;
376
+ else if (text.startsWith("as", i)) kwLen = 2;
377
+ if (kwLen > 0 && i + kwLen < text.length && isSpace(text[i + kwLen])) {
378
+ let after = i + kwLen;
379
+ while (after < text.length && isSpace(text[after])) after++;
380
+ return { head: text.slice(0, wsStart), matchLength: after };
381
+ }
382
+ i = kwStart + 1;
383
+ }
384
+ return void 0;
385
+ }
386
+ function keyCarriesALine(key) {
387
+ return key.toLowerCase().indexOf("line") >= 0;
388
+ }
389
+ function isOnEventAttrName(name) {
390
+ if (name.length < 3 || name[0] !== "o" || name[1] !== "n") return false;
391
+ for (let i = 2; i < name.length; i++) {
392
+ const c = name[i];
393
+ if (c < "a" || c > "z") return false;
394
+ }
395
+ return true;
396
+ }
397
+ var RUNE_LEAF_SEGMENTS = /* @__PURE__ */ new Set([
398
+ "state",
399
+ "raw",
400
+ "snapshot",
401
+ "derived",
402
+ "by",
403
+ "effect",
404
+ "pre",
405
+ "tracking",
406
+ "root",
407
+ "untracked",
408
+ "props",
409
+ "id",
410
+ "is_safe",
411
+ "bindable",
412
+ "host",
413
+ "inspect",
414
+ "trace",
415
+ "render"
416
+ ]);
417
+ var SVELTE_INVOKING_RUNES = /* @__PURE__ */ new Set([
418
+ "$effect",
419
+ "$effect.pre",
420
+ "$effect.root",
421
+ "$effect.untracked",
422
+ "$derived.by",
423
+ "$host"
424
+ ]);
425
+ function isInvokingRune(calleeExpression) {
426
+ return SVELTE_INVOKING_RUNES.has(calleeExpression);
427
+ }
428
+ function isRuneCallee(calleeExpression) {
429
+ if (!calleeExpression.startsWith("$")) return false;
430
+ if (SVELTE_RUNES.has(calleeExpression)) return true;
431
+ const leaf = calleeExpression.split(".").pop() ?? "";
432
+ return RUNE_LEAF_SEGMENTS.has(leaf);
433
+ }
434
+ var DELEGATE_FILE_ACTOR = "(file)";
435
+ var RealSvelteAnalyzer = class _RealSvelteAnalyzer {
436
+ analyze(rootNode, content, filePath, context) {
437
+ const symbols = [];
438
+ const relationships = [];
439
+ const imports = [];
440
+ const exports = [];
441
+ const errors = [];
442
+ const patterns = {};
443
+ const moduleName = path.basename(filePath, path.extname(filePath));
444
+ patterns["Svelte SFC"] = true;
445
+ if (rootNode.hasError) {
446
+ const errStack = [rootNode];
447
+ while (errStack.length > 0) {
448
+ const n = errStack.pop();
449
+ if (n.type === "ERROR" || n.isMissing) {
450
+ errors.push({
451
+ message: `Syntax ${n.type} detected. The Brain Engine is bypassing this broken node.`,
452
+ range: this.nodeToRange(n),
453
+ severity: "warning"
454
+ });
455
+ }
456
+ for (let i = n.childCount - 1; i >= 0; i--) {
457
+ const child = n.child(i);
458
+ if (child) errStack.push(child);
459
+ }
460
+ }
461
+ }
462
+ symbols.push({
463
+ name: moduleName,
464
+ kind: ASTEngine.SymbolKind.Class,
465
+ range: this.nodeToRange(rootNode),
466
+ scope: "",
467
+ visibility: "public",
468
+ metadata: { isSvelteComponent: true, isSfc: true }
469
+ });
470
+ const seenUsages = /* @__PURE__ */ new Set();
471
+ let templateDecisions = 0;
472
+ const templateExprNames = /* @__PURE__ */ new Map();
473
+ const templateReadSites = /* @__PURE__ */ new Map();
474
+ const directiveCalls = [];
475
+ const seenDirectiveCalls = /* @__PURE__ */ new Set();
476
+ const carrierReadKeys = /* @__PURE__ */ new Set();
477
+ const thisTargets = /* @__PURE__ */ new Map();
478
+ const thisTargetSites = /* @__PURE__ */ new Map();
479
+ const dottedTags = /* @__PURE__ */ new Map();
480
+ const templateStack = [
481
+ { node: rootNode, scope: { shadowed: /* @__PURE__ */ new Set() } }
482
+ ];
483
+ while (templateStack.length > 0) {
484
+ const { node: n, scope } = templateStack.pop();
485
+ const t = n.type;
486
+ if (t === "script_element" || t === "style_element") continue;
487
+ const shadowed = scope.shadowed;
488
+ const opensScope = t === "each_statement" || t === "await_statement" || t === "if_statement" || t === "else_if_block" || t === "key_statement" || t === "snippet_statement";
489
+ const childScope = opensScope ? { shadowed: new Set(shadowed) } : scope;
490
+ const childShadowed = childScope.shadowed;
491
+ if (t === "snippet_statement") {
492
+ const nm = this.findDescendant(n, (c) => c.type === "snippet_name");
493
+ if (nm && !seenUsages.has("snippet:" + nm.text)) {
494
+ seenUsages.add("snippet:" + nm.text);
495
+ symbols.push({
496
+ name: nm.text,
497
+ kind: ASTEngine.SymbolKind.Method,
498
+ range: this.nodeToRange(n),
499
+ scope: moduleName,
500
+ ownerName: moduleName,
501
+ ownerKind: ASTEngine.SymbolKind.Class,
502
+ visibility: "public",
503
+ metadata: { isSnippet: true }
504
+ });
505
+ this.collectSnippetParams(n, templateExprNames, childShadowed, templateReadSites);
506
+ }
507
+ }
508
+ if (t === "render_tag") {
509
+ const text = n.text.replace("{@render", "").trim();
510
+ let end = 0;
511
+ while (end < text.length && isIdentifier(text.slice(0, end + 1))) end++;
512
+ let targetName = text.slice(0, end);
513
+ const afterLead = text.slice(end).trimStart();
514
+ if (targetName && (afterLead.startsWith(".") || afterLead.startsWith("?."))) {
515
+ const leaf = this.dottedRenderLeaf(text);
516
+ if (leaf) targetName = leaf;
517
+ }
518
+ const target = targetName ? { text: targetName } : void 0;
519
+ if (target && isIdentifier(target.text)) {
520
+ relationships.push({
521
+ source: moduleName,
522
+ target: target.text,
523
+ type: ASTEngine.RelationshipType.Calls,
524
+ confidence: 0.9,
525
+ // M59 (#825): the `{@render ...}` tag the name is written in.
526
+ line_number: n.startPosition.row + 1,
527
+ metadata: { context: "Svelte {@render} tag" }
528
+ });
529
+ const open = text.indexOf("(");
530
+ if (open >= 0) {
531
+ const args = text.slice(open + 1, text.endsWith(")") ? text.length - 1 : text.length);
532
+ if (args.trim()) {
533
+ this.collectIdentifiersFromText(
534
+ args,
535
+ templateExprNames,
536
+ childShadowed,
537
+ n.startPosition.row + 1 + _RealSvelteAnalyzer.linesBefore(text, open + 1),
538
+ templateReadSites
539
+ );
540
+ }
541
+ }
542
+ }
543
+ }
544
+ if (t === "if_statement" || t === "else_if_block" || t === "each_statement" || t === "await_statement" || t === "key_statement") {
545
+ templateDecisions++;
546
+ }
547
+ if (t === "each_statement") {
548
+ this.collectEachExpression(n, templateExprNames, childShadowed, templateReadSites);
549
+ }
550
+ if (t === "await_statement") {
551
+ this.collectAwaitExpression(n, templateExprNames, childShadowed, templateReadSites);
552
+ }
553
+ if (t === "if_statement" || t === "else_if_block" || t === "key_statement") {
554
+ this.collectConditionExpression(n, templateExprNames, childShadowed, templateReadSites);
555
+ }
556
+ if (t === "expression") {
557
+ this.collectExpressionIdentifiers(n, templateExprNames, childShadowed, templateReadSites);
558
+ }
559
+ if (t === "html_tag" || t === "const_tag" || t === "debug_tag") {
560
+ this.collectTagExpressionIdentifiers(n, templateExprNames, childShadowed, templateReadSites);
561
+ if (t === "const_tag") {
562
+ const body = this.expressionText(n);
563
+ const eq = this.topLevelEq(body);
564
+ if (eq >= 0) this.collectShadowNames(body.slice(0, eq), childShadowed);
565
+ }
566
+ }
567
+ if (t === "element" || t === "self_closing_tag") {
568
+ const tag = this.elementTagName(n);
569
+ if (tag && tag.startsWith("svelte:") && !SVELTE_BUILTIN_TAGS.has(tag)) {
570
+ } else if (tag && tag.includes(".") && !seenUsages.has(tag)) {
571
+ seenUsages.add(tag);
572
+ const tagNode = this.elementTagNameNode(n);
573
+ const nsSegment = tag.slice(0, tag.indexOf("."));
574
+ dottedTags.set(tag, {
575
+ line: tagNode ? tagNode.startPosition.row + 1 : 0,
576
+ templateLocal: shadowed.has(nsSegment)
577
+ });
578
+ } else if (tag && !HTML_BUILTIN_TAGS.has(tag) && !SVELTE_BUILTIN_TAGS.has(tag) && !seenUsages.has(tag)) {
579
+ seenUsages.add(tag);
580
+ const tagNode = this.elementTagNameNode(n);
581
+ relationships.push({
582
+ source: moduleName,
583
+ target: tag,
584
+ // RENDERS, NOT INSTANTIATES (2026-10-23, extended vocabulary). A component tag creates
585
+ // an element the HOST RENDERER mounts; the source contains no `new`, so an
586
+ // `instantiates` edge claimed an allocation site the file does not have (types.ts'
587
+ // Renders contract). `instantiates` keeps its OWN sites: the JS/TS delegate's script
588
+ // `new` rows, copied by the seam below — the two relations stay distinct.
589
+ type: ASTEngine.RelationshipType.Renders,
590
+ confidence: 0.95,
591
+ // M59 (#825): the `<Child/>` tag as written.
592
+ ...tagNode ? { line_number: tagNode.startPosition.row + 1 } : {},
593
+ metadata: { isComponentUsage: true }
594
+ });
595
+ }
596
+ if (tag === "svelte:component" || tag === "svelte:element") {
597
+ const thisTag = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
598
+ if (thisTag) {
599
+ for (let i = 0; i < thisTag.namedChildCount; i++) {
600
+ const attr = thisTag.namedChild(i);
601
+ if (!attr || attr.type !== "attribute" || this.attributeName(attr) !== "this") continue;
602
+ const valueNode = this.findChildByType(attr, "expression");
603
+ if (!valueNode) continue;
604
+ let vt = valueNode.text.trim();
605
+ if (vt.startsWith("{") && vt.endsWith("}")) vt = vt.slice(1, -1).trim();
606
+ if (vt && isIdentifier(vt)) {
607
+ const siteLine = valueNode.startPosition.row + 1;
608
+ thisTargets.set(vt, siteLine);
609
+ const prior = thisTargetSites.get(vt);
610
+ if (prior) prior.push(siteLine);
611
+ else thisTargetSites.set(vt, [siteLine]);
612
+ }
613
+ }
614
+ }
615
+ }
616
+ const startTag2 = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
617
+ if (startTag2) {
618
+ for (let i = 0; i < startTag2.namedChildCount; i++) {
619
+ const attr = startTag2.namedChild(i);
620
+ if (!attr || attr.type !== "attribute") continue;
621
+ const attrName = this.attributeName(attr);
622
+ if (!attrName || attrName.startsWith("on:")) continue;
623
+ if (this.findChildByType(attr, "expression")) continue;
624
+ if (attrName.startsWith("{") && attrName.endsWith("}")) {
625
+ const inner = attrName.slice(1, -1).trim();
626
+ const expr = inner.startsWith("...") ? inner.slice(3) : inner;
627
+ this.collectIdentifiersFromText(expr, templateExprNames, childShadowed, attr.startPosition.row + 1, templateReadSites);
628
+ }
629
+ }
630
+ }
631
+ for (let i = 0; startTag2 && i < startTag2.namedChildCount; i++) {
632
+ const attr = startTag2.namedChild(i);
633
+ if (!attr || attr.type !== "attribute") continue;
634
+ const attrName = this.attributeName(attr);
635
+ if (!attrName) continue;
636
+ const colon = attrName.indexOf(":");
637
+ if (colon <= 0) continue;
638
+ const prefix = attrName.slice(0, colon);
639
+ const rest = attrName.slice(colon + 1);
640
+ if (!rest) continue;
641
+ if (attrName.startsWith("on:")) continue;
642
+ if (SVELTE_DECLARING_DIRECTIVES.has(prefix)) continue;
643
+ const exprNode = this.findChildByType(attr, "expression");
644
+ if (SVELTE_MEMBER_READ_DIRECTIVES.has(prefix)) {
645
+ const text = exprNode ? this.expressionText(exprNode) : "";
646
+ if (!text) continue;
647
+ const base = exprNode ? exprNode.startPosition.row + 1 : attr.startPosition.row + 1;
648
+ const before = new Set(templateExprNames.keys());
649
+ this.collectIdentifiersFromText(text, templateExprNames, childShadowed, base, templateReadSites);
650
+ for (const nm of templateExprNames.keys()) if (!before.has(nm)) carrierReadKeys.add(`${nm}@${templateExprNames.get(nm)}`);
651
+ continue;
652
+ }
653
+ if (SVELTE_INVOKED_DIRECTIVES.has(prefix)) {
654
+ if (rest === moduleName) continue;
655
+ const leaf = rest.includes(".") ? rest.slice(rest.lastIndexOf(".") + 1) : rest;
656
+ if (!leaf) continue;
657
+ if (NON_CALLABLE_LITERALS.has(leaf)) continue;
658
+ seenDirectiveCalls.add(`${leaf}@${attr.startPosition.row + 1}`);
659
+ directiveCalls.push({
660
+ leaf,
661
+ line: attr.startPosition.row + 1,
662
+ context: `Svelte ${prefix} directive ${attrName}`
663
+ });
664
+ }
665
+ }
666
+ const startTag = this.findChildByType(n, "start_tag") || this.findChildByType(n, "self_closing_tag");
667
+ if (startTag) {
668
+ for (let i = 0; i < startTag.namedChildCount; i++) {
669
+ const attr = startTag.namedChild(i);
670
+ if (!attr || attr.type !== "attribute") continue;
671
+ const attrName = this.attributeName(attr);
672
+ if (!attrName) continue;
673
+ const isOnDirective = attrName.startsWith("on:");
674
+ const isOnclickAttr = !isOnDirective && isOnEventAttrName(attrName) && attrName.length > 4 && !!tag && tag[0] === tag[0].toLowerCase() && !tag.includes(".") && (!tag.includes(":") || tag.startsWith("svelte:"));
675
+ if (!isOnDirective && !isOnclickAttr) continue;
676
+ const handlerSite = this.onHandlerCall(attr);
677
+ const handler = handlerSite?.name;
678
+ if (handler && handler !== moduleName) {
679
+ relationships.push({
680
+ source: moduleName,
681
+ target: handler,
682
+ type: ASTEngine.RelationshipType.Calls,
683
+ confidence: 0.85,
684
+ ...typeof handlerSite.line === "number" ? { line_number: handlerSite.line } : {},
685
+ metadata: {
686
+ context: isOnDirective ? `Svelte event directive ${attrName}` : `Svelte event attribute ${attrName}`
687
+ }
688
+ });
689
+ }
690
+ }
691
+ for (let i = 0; i < startTag.childCount; i++) {
692
+ const directive = startTag.child(i);
693
+ if (!directive || directive.type !== "ERROR") continue;
694
+ const kids = [];
695
+ for (let j = 0; j < directive.childCount; j++) {
696
+ const c = directive.child(j);
697
+ if (c) kids.push(c);
698
+ }
699
+ const attachIdx = kids.findIndex((k) => k.type === "ERROR" && k.text === "attach");
700
+ if (attachIdx < 0) continue;
701
+ const nameTok = kids[attachIdx + 1];
702
+ if (!nameTok || !isIdentifier(nameTok.text)) continue;
703
+ const afterText = (kids[attachIdx + 2]?.text ?? "}").trim();
704
+ if (afterText !== "}" && afterText !== "(") continue;
705
+ const attachHandler = nameTok.text;
706
+ if (attachHandler === moduleName) continue;
707
+ relationships.push({
708
+ source: moduleName,
709
+ target: attachHandler,
710
+ type: ASTEngine.RelationshipType.Calls,
711
+ confidence: 0.8,
712
+ line_number: nameTok.startPosition.row + 1,
713
+ metadata: { context: "Svelte attach directive" }
714
+ });
715
+ }
716
+ }
717
+ }
718
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
719
+ const child = n.namedChild(i);
720
+ if (child) templateStack.push({ node: child, scope: childScope });
721
+ }
722
+ }
723
+ const scriptTrees = context?.sfcScriptTrees ?? [];
724
+ const scriptLines = context?.sfcScriptLines ?? [];
725
+ const scriptCols = context?.sfcScriptCols ?? [];
726
+ const scriptStartIdx = context?.sfcScriptStartIndex ?? [];
727
+ let scriptDecisions = 0;
728
+ for (let bi = 0; bi < scriptTrees.length; bi++) {
729
+ const scriptTree = scriptTrees[bi];
730
+ const scriptStartLine = scriptLines[bi];
731
+ const scriptStartColumn = scriptCols[bi];
732
+ const scriptBlockIndex = scriptStartIdx[bi] ?? 0;
733
+ if (!scriptTree || scriptStartLine === void 0) continue;
734
+ const scriptContent = scriptTree.rootNode?.text ?? "";
735
+ const lineOffset = scriptStartLine - 1;
736
+ const colOffset = scriptStartColumn ?? 0;
737
+ const isTs = context?.sfcScriptLangs?.[bi] === "ts" || context?.sfcScriptLangs?.[bi] === "tsx";
738
+ let sub;
739
+ try {
740
+ sub = isTs ? new RealTypeScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath) : new RealJavaScriptAnalyzer().analyze(scriptTree.rootNode, scriptContent, filePath, context);
741
+ } catch (e) {
742
+ sub = { symbols: [], relationships: [], imports: [], exports: [], errors: [], metadata: {} };
743
+ errors.push({
744
+ message: `script sub-parse failed: ${e?.message?.slice(0, 120)}`,
745
+ range: { startLine: scriptStartLine, startColumn: 0, endLine: scriptStartLine, endColumn: 0 },
746
+ severity: "warning"
747
+ });
748
+ }
749
+ this.extractSvelteDeclarations(scriptTree.rootNode, moduleName, symbols, lineOffset, colOffset, scriptBlockIndex);
750
+ this.extractRuneInvocations(scriptTree.rootNode, moduleName, relationships, lineOffset);
751
+ const isModuleBlock = this.isModuleContextBlock(rootNode, bi);
752
+ const runePropNames = /* @__PURE__ */ new Set();
753
+ this.collectPropsDestructuredNames(scriptTree.rootNode, runePropNames);
754
+ const runePropNamesLowered = new Set(scriptContent.length > 0 ? [...runePropNames] : []);
755
+ const reactiveTargetNames = /* @__PURE__ */ new Set();
756
+ this.collectReactiveTargetNames(scriptTree.rootNode, reactiveTargetNames);
757
+ for (const s of sub.symbols ?? []) {
758
+ if (runePropNamesLowered.has(s.name ?? "")) continue;
759
+ if (reactiveTargetNames.has(s.name ?? "")) continue;
760
+ const firstLine = s.range.startLine === 1;
761
+ s.range.startLine += lineOffset;
762
+ s.range.endLine += lineOffset;
763
+ if (firstLine && colOffset) {
764
+ s.range.startColumn += colOffset;
765
+ if (s.range.endLine === s.range.startLine) s.range.endColumn += colOffset;
766
+ }
767
+ if (scriptBlockIndex && s.range.startIndex !== void 0) s.range.startIndex += scriptBlockIndex;
768
+ if (scriptBlockIndex && s.range.endIndex !== void 0) s.range.endIndex += scriptBlockIndex;
769
+ shiftMetadataLineMirrors(s.metadata, lineOffset);
770
+ if (!isModuleBlock && this.isExported(sub.exports ?? [], s.name)) {
771
+ const wasVariable = s.kind === ASTEngine.SymbolKind.Variable;
772
+ if (wasVariable) {
773
+ s.kind = ASTEngine.SymbolKind.Property;
774
+ s.metadata = { ...s.metadata ?? {}, isSvelteProp: true };
775
+ }
776
+ s.ownerName = moduleName;
777
+ s.ownerKind = ASTEngine.SymbolKind.Class;
778
+ s.scope = moduleName;
779
+ }
780
+ symbols.push(s);
781
+ }
782
+ const moduleExportNames = new Set(
783
+ isModuleBlock ? (sub.exports ?? []).map((e) => e.name) : []
784
+ );
785
+ const delegateBases = /* @__PURE__ */ new Map();
786
+ for (const rb of sub.relationships ?? []) {
787
+ if (rb?.type !== ASTEngine.RelationshipType.Extends) continue;
788
+ if (typeof rb.source === "string" && rb.source !== "" && typeof rb.target === "string" && rb.target !== "") {
789
+ delegateBases.set(rb.source, rb.target);
790
+ }
791
+ }
792
+ for (const r of sub.relationships ?? []) {
793
+ const tgt = r.target ?? "";
794
+ if (SVELTE_RUNES.has(tgt)) continue;
795
+ if (r.type === ASTEngine.RelationshipType.Contains && isModuleBlock && moduleExportNames.has(tgt)) continue;
796
+ const callee = r.metadata?.calleeExpression ?? "";
797
+ if (isRuneCallee(callee) || r.source === "(script)" && SVELTE_RUNES.has(tgt)) continue;
798
+ if (typeof r.line_number === "number") r.line_number += lineOffset;
799
+ shiftMetadataLineMirrors(r.metadata, lineOffset);
800
+ if (r.source === DELEGATE_FILE_ACTOR) r.source = moduleName;
801
+ if (r.type === ASTEngine.RelationshipType.Imports) {
802
+ const mod = r.metadata?.module;
803
+ if (typeof mod === "string" && mod !== "" && typeof r.target === "string" && r.target !== "" && !r.target.includes(":") && !r.target.includes("/")) {
804
+ r.target = `${mod}:${r.target}`;
805
+ }
806
+ }
807
+ if (r.type === ASTEngine.RelationshipType.Overrides) {
808
+ if (typeof r.source === "string" && typeof r.target === "string" && r.target !== "" && !r.target.includes(".")) {
809
+ const owner = r.source.slice(0, r.source.lastIndexOf("."));
810
+ const base = owner === "" ? void 0 : delegateBases.get(owner);
811
+ if (base) r.target = `${base}.${r.target}`;
812
+ }
813
+ }
814
+ r.metadata = { ...r.metadata ?? {}, sfcDelegated: true, sfcLineOffset: lineOffset };
815
+ relationships.push(r);
816
+ }
817
+ for (const i of sub.imports ?? []) {
818
+ if (i.range) {
819
+ const firstLine = i.range.startLine === 1;
820
+ i.range.startLine += lineOffset;
821
+ i.range.endLine += lineOffset;
822
+ if (firstLine && colOffset) {
823
+ i.range.startColumn += colOffset;
824
+ if (i.range.endLine === i.range.startLine) i.range.endColumn += colOffset;
825
+ }
826
+ if (scriptBlockIndex && i.range.startIndex !== void 0) i.range.startIndex += scriptBlockIndex;
827
+ if (scriptBlockIndex && i.range.endIndex !== void 0) i.range.endIndex += scriptBlockIndex;
828
+ }
829
+ imports.push(i);
830
+ }
831
+ if (isModuleBlock) {
832
+ for (const e of sub.exports ?? []) {
833
+ const r = e.range;
834
+ if (r) {
835
+ const firstLine = r.startLine === 1;
836
+ r.startLine += lineOffset;
837
+ r.endLine += lineOffset;
838
+ if (firstLine && colOffset) {
839
+ r.startColumn += colOffset;
840
+ if (r.endLine === r.startLine) r.endColumn += colOffset;
841
+ }
842
+ if (scriptBlockIndex && r.startIndex !== void 0) r.startIndex += scriptBlockIndex;
843
+ if (scriptBlockIndex && r.endIndex !== void 0) r.endIndex += scriptBlockIndex;
844
+ }
845
+ exports.push(e);
846
+ }
847
+ }
848
+ for (const er of sub.errors ?? []) {
849
+ const r = er.range;
850
+ if (r) {
851
+ const firstLine = r.startLine === 1;
852
+ r.startLine += lineOffset;
853
+ r.endLine += lineOffset;
854
+ if (firstLine && typeof r.startColumn === "number") {
855
+ r.startColumn += colOffset;
856
+ r.endColumn += colOffset;
857
+ }
858
+ if (typeof r.startIndex === "number") {
859
+ r.startIndex += scriptBlockIndex;
860
+ r.endIndex += scriptBlockIndex;
861
+ }
862
+ }
863
+ errors.push(er);
864
+ }
865
+ scriptDecisions += Math.max(0, (sub.metadata?.cyclomaticComplexity ?? 1) - 1);
866
+ }
867
+ if (templateExprNames.size > 0) {
868
+ const memberNames = new Set(symbols.map((s) => s.name));
869
+ const seenUses = /* @__PURE__ */ new Set();
870
+ for (const [name, line] of templateExprNames) {
871
+ let resolved = name;
872
+ if (!memberNames.has(name) && name.startsWith("$") && !name.startsWith("$$") && memberNames.has(name.slice(1))) {
873
+ resolved = name.slice(1);
874
+ }
875
+ if (!memberNames.has(resolved)) continue;
876
+ const key = `${moduleName}:${resolved}`;
877
+ if (seenUses.has(key)) continue;
878
+ seenUses.add(key);
879
+ const sites = templateReadSites.get(name) ?? (resolved === name ? void 0 : templateReadSites.get(resolved));
880
+ relationships.push({
881
+ source: moduleName,
882
+ target: resolved,
883
+ type: ASTEngine.RelationshipType.Uses,
884
+ confidence: 0.95,
885
+ // M59 (#825): the line the identifier is WRITTEN on, recorded where
886
+ // the text was scanned. `0` can only appear if a caller passed no
887
+ // base line for a fragment it could not locate — the oracle counts
888
+ // that as a wrong line rather than a silent pass. This is the LAST site;
889
+ // the whole set is in `templateReadSites` below.
890
+ ...line > 0 ? { line_number: line } : {},
891
+ metadata: {
892
+ isTemplateExpression: true,
893
+ // D12: present only for a carrier-sourced read. Absent means the read came
894
+ // from a block construct, a store subscription or a tag expression, so a
895
+ // consumer can tell a two-way binding from an ordinary template read.
896
+ ...carrierReadKeys.has(`${resolved}@${line}`) ? { carrierDirective: true } : {},
897
+ // Present only when the member is read on MORE THAN ONE site, so a
898
+ // single-read edge carries no extra key. `templateReadSitesCollapsed`
899
+ // is the machine-readable statement that `line_number` above is one
900
+ // of several and not the only place to look.
901
+ ...sites && sites.length > 1 ? { templateReadSites: [...sites], templateReadSitesCollapsed: sites.length } : {}
902
+ }
903
+ });
904
+ }
905
+ }
906
+ if (dottedTags.size > 0) {
907
+ const nsBindings = /* @__PURE__ */ new Map();
908
+ for (const imp of imports) {
909
+ for (const b of this.importLocalBindings(imp)) nsBindings.set(b.name, b.module);
910
+ }
911
+ const declaredNames = new Set(symbols.map((s) => s.name));
912
+ for (const [tag, site] of dottedTags) {
913
+ const dot = tag.indexOf(".");
914
+ const ns = tag.slice(0, dot);
915
+ const module = nsBindings.get(ns);
916
+ if (module === void 0 && !site.templateLocal && !declaredNames.has(ns)) continue;
917
+ relationships.push({
918
+ source: moduleName,
919
+ target: tag,
920
+ // RENDERS: the dotted tag is a component render, exactly like the plain tag above.
921
+ type: ASTEngine.RelationshipType.Renders,
922
+ confidence: 0.9,
923
+ ...site.line > 0 ? { line_number: site.line } : {},
924
+ metadata: {
925
+ isComponentUsage: true,
926
+ resolution: "namespace-member",
927
+ // The MODULE is deliberately NOT copied onto the edge. It is already in
928
+ // this same result's `imports` records, keyed by `namespaceBinding`, and
929
+ // repeating a module specifier on every edge cost 45 bytes × 7,802 corpus
930
+ // edges: measured +903 KiB (+16.6%) on shadcn-svelte's serialized
931
+ // relationship payload before this was removed, for information the
932
+ // consumer already had. `namespaceBinding` is the key into it.
933
+ ...module !== void 0 ? { namespaceBinding: ns } : {},
934
+ ...module === void 0 && site.templateLocal ? { namespaceLocal: true } : {},
935
+ ...module === void 0 && !site.templateLocal ? { namespaceSymbol: true } : {}
936
+ }
937
+ });
938
+ }
939
+ }
940
+ if (thisTargets.size > 0) {
941
+ const memberNames = new Set(symbols.map((s) => s.name));
942
+ const importBindings = /* @__PURE__ */ new Set();
943
+ for (const imp of imports) {
944
+ for (const b of this.importLocalBindings(imp)) importBindings.add(b.name);
945
+ }
946
+ for (const [name, line] of thisTargets) {
947
+ if (memberNames.has(name) || !importBindings.has(name)) continue;
948
+ const sites = thisTargetSites.get(name);
949
+ relationships.push({
950
+ source: moduleName,
951
+ target: name,
952
+ // RENDERS: a `<svelte:component this={X}/>` mounts X — a component render, not a `new`.
953
+ type: ASTEngine.RelationshipType.Renders,
954
+ confidence: 0.95,
955
+ // M59 (#825): the `this={X}` value's line. F22: the LAST site, kept
956
+ // deliberately — see the site map's declaration.
957
+ ...line > 0 ? { line_number: line } : {},
958
+ metadata: {
959
+ isComponentUsage: true,
960
+ // F22: the full site set, sorted ascending so two runs agree.
961
+ //
962
+ // EMITTED FOR A SINGLE SITE TOO, and that is a PIN rather than a
963
+ // preference: `svelte/dynamic-target-sites` requires
964
+ // `dynamicTargetLines: [30]` for its one-site `Panel`, while
965
+ // `svelte/multi-site-read-record` requires the read map to be ABSENT
966
+ // (`templateReadSites: null`) for its one-site reads. The two
967
+ // conventions therefore DIVERGE, and the divergence is recorded here
968
+ // rather than smoothed over: unifying them is a payload change on one
969
+ // fixture or the other, not a fix, and it belongs to whichever round
970
+ // decides which of the two shapes a consumer should read.
971
+ ...sites && sites.length > 0 ? { dynamicTargetLines: [...sites].sort((a, b) => a - b) } : {}
972
+ }
973
+ });
974
+ }
975
+ }
976
+ for (const s of symbols) {
977
+ if (s.ownerName) {
978
+ relationships.push({
979
+ source: s.ownerName,
980
+ target: s.name,
981
+ type: ASTEngine.RelationshipType.Contains,
982
+ confidence: 1,
983
+ // M59 (#825): the MEMBER's declaration line (already in SFC
984
+ // coordinates) — the edge names the member.
985
+ ...s.range?.startLine ? { line_number: s.range.startLine } : {},
986
+ metadata: {}
987
+ });
988
+ }
989
+ }
990
+ const architecturalScore = Math.min(100, Math.max(0, 100 - errors.length * 5));
991
+ const detectedPatterns = Object.keys(patterns);
992
+ const frameworkPatterns = detectedPatterns.map((p) => ({
993
+ framework: "Svelte",
994
+ patterns: [{ type: "usage", detected: true, description: p }]
995
+ }));
996
+ for (const dc of directiveCalls) {
997
+ relationships.push({
998
+ source: moduleName,
999
+ target: dc.leaf,
1000
+ type: ASTEngine.RelationshipType.Calls,
1001
+ confidence: 0.85,
1002
+ line_number: dc.line,
1003
+ metadata: { context: dc.context }
1004
+ });
1005
+ }
1006
+ const relSeen = /* @__PURE__ */ new Map();
1007
+ const uniqueRelationships = [];
1008
+ for (const r of relationships) {
1009
+ const key = `${String(r.type)}|${String(r.source)}|${String(r.target)}`;
1010
+ const prior = relSeen.get(key);
1011
+ if (!prior) {
1012
+ relSeen.set(key, r);
1013
+ uniqueRelationships.push(r);
1014
+ continue;
1015
+ }
1016
+ _RealSvelteAnalyzer.recordDroppedTemplateRead(prior, r);
1017
+ }
1018
+ relationships.length = 0;
1019
+ relationships.push(...uniqueRelationships);
1020
+ this.publishResolutionClaims(relationships, imports, symbols, filePath, context?.projectRoot);
1021
+ this.classifyEvidenceTier(relationships);
1022
+ const cyclomaticComplexity = 1 + templateDecisions + scriptDecisions;
1023
+ return {
1024
+ language: "svelte",
1025
+ symbols,
1026
+ relationships,
1027
+ imports,
1028
+ exports,
1029
+ errors,
1030
+ metadata: {
1031
+ // Real cyclomatic complexity: script decisions + template decision
1032
+ // blocks. Was the synthetic symbols+relationships heuristic with a
1033
+ // hardcoded cyclomatic of 1 (fourth analyzer with this disease).
1034
+ complexity: cyclomaticComplexity,
1035
+ linesOfCode: content.split("\n").length,
1036
+ cyclomaticComplexity,
1037
+ framework: "Svelte",
1038
+ patterns: detectedPatterns,
1039
+ detectedFrameworks: ["Svelte"],
1040
+ frameworkPatterns,
1041
+ architecturalInsights: [],
1042
+ architecturalScore,
1043
+ hasFrameworkIntelligence: true,
1044
+ hasTypeInference: false
1045
+ }
1046
+ };
1047
+ }
1048
+ // ============================================================================
1049
+ // SVELTE-SPECIFIC DECLARATION EXTRACTION
1050
+ // ============================================================================
1051
+ /**
1052
+ * S2 (#419): the binding names declared by a $props() destructure
1053
+ * (`let { a, b, c = 5 } = $props()`). These are component-owned Properties
1054
+ * emitted by extractSvelteDeclarations; the JS sub-analyzer's parallel
1055
+ * destructured Variables are dropped to avoid double declaration.
1056
+ */
1057
+ collectPropsDestructuredNames(root, out) {
1058
+ const stack = [root];
1059
+ while (stack.length > 0) {
1060
+ const n = stack.pop();
1061
+ if (n.type === "call_expression") {
1062
+ const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
1063
+ if (callee && (callee.text === "$props" || callee.text === "$props()")) {
1064
+ const declarator = this.findAncestor(n, (p) => p.type === "variable_declarator");
1065
+ if (declarator) {
1066
+ const pattern = declarator.childForFieldName?.("name") ?? this.findChildByType(declarator, "object_pattern");
1067
+ const names = pattern ? this.objectPatternNames(pattern) : [];
1068
+ names.forEach((nm) => out.add(nm));
1069
+ }
1070
+ }
1071
+ }
1072
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
1073
+ const c = n.namedChild(i);
1074
+ if (c) stack.push(c);
1075
+ }
1076
+ }
1077
+ }
1078
+ objectPatternNames(pattern) {
1079
+ return this.objectPatternPairs(pattern).map((p) => p.name);
1080
+ }
1081
+ /**
1082
+ * M59 (#825) follow-up, measured on a `$props()` rename destructure: the
1083
+ * pair VALUE binds, not the key (`let { renamed: local }` binds `local`).
1084
+ * The old key-side naming emitted a phantom Property `renamed` AND defeated
1085
+ * the merge dedupe (the JS delegate's correct `local` survived alongside
1086
+ * it — one binding, two symbols). Each pair also carries its node so the
1087
+ * symbol ranges at its own NAME line, not the whole declarator.
1088
+ */
1089
+ objectPatternPairs(pattern) {
1090
+ const out = [];
1091
+ const take = (n) => {
1092
+ if (n.type === "identifier" || n.type === "shorthand_property_identifier" || n.type === "shorthand_property_identifier_pattern") {
1093
+ out.push({ name: n.text, node: n });
1094
+ } else if (n.type === "pair_pattern") {
1095
+ const value = n.childForFieldName?.("value");
1096
+ if (value) take(value);
1097
+ } else if (n.type === "rest_pattern") {
1098
+ const inner = n.namedChild(0);
1099
+ if (inner) take(inner);
1100
+ } else if (n.type === "assignment_pattern") {
1101
+ const left = n.childForFieldName?.("left") ?? n.namedChild(0);
1102
+ if (left) take(left);
1103
+ } else if (n.type === "object_assignment_pattern") {
1104
+ const left = n.childForFieldName?.("left") ?? n.childForFieldName?.("pattern") ?? n.namedChild(0);
1105
+ if (left) take(left);
1106
+ } else if (
1107
+ // `object_assignment_pattern_deprecated` was a dead disjunct: measured
1108
+ // 2026-10-08, the pinned svelte grammar declares no assignment-pattern
1109
+ // type at all, and the pinned typescript/javascript grammars declare
1110
+ // `object_assignment_pattern` and `assignment_pattern` with no deprecated
1111
+ // spelling. A real parse of `let { a = 1, ...rest } = obj` builds
1112
+ // `object_pattern` -> `object_assignment_pattern`, which is the shape the
1113
+ // arm ABOVE already handles; this container branch is for the patterns
1114
+ // whose children are all bindings.
1115
+ n.type === "object_pattern" || n.type === "array_pattern"
1116
+ ) {
1117
+ for (let i = 0; i < n.namedChildCount; i++) {
1118
+ const c = n.namedChild(i);
1119
+ if (c) take(c);
1120
+ }
1121
+ }
1122
+ };
1123
+ take(pattern);
1124
+ return out;
1125
+ }
1126
+ /**
1127
+ * S6 (#419): collect the target names of every `$:` reactive declaration
1128
+ * (`$: doubled = expr` → `doubled`). Used at the merge to drop the JS/TS
1129
+ * sub-analyzer's PLACE COPY of such a variable, so the component-owned
1130
+ * reactive symbol emitted by extractSvelteDeclarations is the ONLY one —
1131
+ * the same dedupe S2 applies to $props() bindings.
1132
+ */
1133
+ collectReactiveTargetNames(root, out) {
1134
+ const stack = [root];
1135
+ while (stack.length > 0) {
1136
+ const n = stack.pop();
1137
+ if (n.type === "labeled_statement") {
1138
+ const label = n.namedChild(0);
1139
+ if (label && label.text === "$") {
1140
+ const assignment = this.findDescendant(n, (c) => c.type === "assignment_expression");
1141
+ const left = assignment?.childForFieldName?.("left") ?? assignment?.namedChild(0);
1142
+ if (left && left.type === "identifier") out.add(left.text);
1143
+ }
1144
+ }
1145
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
1146
+ const c = n.namedChild(i);
1147
+ if (c) stack.push(c);
1148
+ }
1149
+ }
1150
+ }
1151
+ /** D7 (RULED 2026-09-27): `calls` edges for named functions passed to an
1152
+ * INVOKING rune. See SVELTE_INVOKING_RUNES for why the set is the invoking
1153
+ * half of the rune surface and not all of it.
1154
+ *
1155
+ * Only a BARE IDENTIFIER argument produces an edge. An arrow function is not a
1156
+ * name and its body is already walked; a call argument (`$effect(doThing())`)
1157
+ * is a use, not a call of `doThing`; a member expression (`$effect(obj.m)`)
1158
+ * names a method the analyzer does not resolve here. Emitting for those would
1159
+ * be the fabrication class this lane already fixed twice.
1160
+ */
1161
+ extractRuneInvocations(root, componentName, relationships, lineOffset) {
1162
+ const seen = /* @__PURE__ */ new Set();
1163
+ const stack = [root];
1164
+ while (stack.length > 0) {
1165
+ const n = stack.pop();
1166
+ if (n.type === "call_expression") {
1167
+ const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
1168
+ const calleeText = callee?.text ?? "";
1169
+ if (isInvokingRune(calleeText)) {
1170
+ const argsField = n.childForFieldName?.("arguments");
1171
+ const argList = [];
1172
+ if (argsField) {
1173
+ for (let i = 0; i < argsField.namedChildCount; i++) {
1174
+ const a = argsField.namedChild(i);
1175
+ if (a) argList.push(a);
1176
+ }
1177
+ }
1178
+ const line = this.nodeToRange(n).startLine + lineOffset;
1179
+ for (const arg of argList) {
1180
+ if (arg.type !== "identifier") continue;
1181
+ const name = arg.text;
1182
+ if (name === componentName || isRuneCallee(name)) continue;
1183
+ const key = `${calleeText}|${name}|${line}`;
1184
+ if (seen.has(key)) continue;
1185
+ seen.add(key);
1186
+ relationships.push({
1187
+ source: componentName,
1188
+ target: name,
1189
+ type: ASTEngine.RelationshipType.Calls,
1190
+ confidence: 0.85,
1191
+ line_number: line,
1192
+ metadata: { context: `Svelte rune ${calleeText}`, runeCallee: calleeText, evidenceClass: "structural" }
1193
+ });
1194
+ }
1195
+ }
1196
+ }
1197
+ for (let i = 0; i < n.namedChildCount; i++) {
1198
+ const c = n.namedChild(i);
1199
+ if (c) stack.push(c);
1200
+ }
1201
+ }
1202
+ }
1203
+ extractSvelteDeclarations(root, componentName, symbols, lineOffset, colOffset, indexOffset = 0) {
1204
+ const stack = [root];
1205
+ const reactiveTargets = /* @__PURE__ */ new Set();
1206
+ while (stack.length > 0) {
1207
+ const n = stack.pop();
1208
+ if (n.type === "call_expression") {
1209
+ const callee = n.childForFieldName?.("function") ?? n.namedChild(0);
1210
+ if (callee && (callee.text === "$props" || callee.text === "$props()")) {
1211
+ const declarator = this.findAncestor(n, (p) => p.type === "variable_declarator");
1212
+ if (declarator) {
1213
+ const pattern = declarator.childForFieldName?.("name") ?? this.findChildByType(declarator, "object_pattern");
1214
+ const pairs = pattern && pattern.type === "object_pattern" ? this.objectPatternPairs(pattern) : [];
1215
+ for (const { name, node: nameNode } of pairs) {
1216
+ symbols.push({
1217
+ name,
1218
+ kind: ASTEngine.SymbolKind.Property,
1219
+ range: this.offsetRange(this.nodeToRange(nameNode), lineOffset, colOffset, indexOffset),
1220
+ scope: componentName,
1221
+ ownerName: componentName,
1222
+ ownerKind: ASTEngine.SymbolKind.Class,
1223
+ visibility: "public",
1224
+ metadata: { isSvelteProp: true, fromRunes: true }
1225
+ });
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+ if (n.type === "labeled_statement") {
1231
+ const label = n.namedChild(0);
1232
+ if (label && label.text === "$") {
1233
+ const assignment = this.findDescendant(n, (c) => c.type === "assignment_expression");
1234
+ if (assignment) {
1235
+ const left = assignment.childForFieldName?.("left") ?? assignment.namedChild(0);
1236
+ if (left && left.type === "identifier") {
1237
+ if (reactiveTargets.has(left.text)) continue;
1238
+ reactiveTargets.add(left.text);
1239
+ symbols.push({
1240
+ name: left.text,
1241
+ kind: ASTEngine.SymbolKind.Variable,
1242
+ range: this.offsetRange(this.nodeToRange(assignment), lineOffset, colOffset, indexOffset),
1243
+ scope: componentName,
1244
+ ownerName: componentName,
1245
+ ownerKind: ASTEngine.SymbolKind.Class,
1246
+ visibility: "public",
1247
+ metadata: { isReactiveDeclaration: true }
1248
+ });
1249
+ }
1250
+ }
1251
+ }
1252
+ }
1253
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
1254
+ const c = n.namedChild(i);
1255
+ if (c) stack.push(c);
1256
+ }
1257
+ }
1258
+ }
1259
+ /**
1260
+ * Is the bi-th <script> block a MODULE block? Module-block exports are
1261
+ * module-level API - they must NOT become component props.
1262
+ *
1263
+ * Svelte spells this two ways and BOTH are real: `context="module"` and the
1264
+ * bare `module` attribute (the Svelte 3/4 shorthand, still what the corpus
1265
+ * writes - `sv-addons/registry/template/src/lib/registry/ui/button/button.svelte`
1266
+ * opens `<script lang="ts" module>`). The old predicate compared the whole
1267
+ * start-tag TEXT for the literal `context="module"`, so every bare-`module`
1268
+ * block was read as an INSTANCE block: its exports became component props
1269
+ * (ownerName = the component, plus a `contains` edge) and the `exports[]`
1270
+ * channel stayed empty. Measured on the 200-file sample: exports[] 0 rows,
1271
+ * and the 22 `exports` edges all arrived from the JS delegate's own
1272
+ * `export const` handling rather than from the module API they are.
1273
+ *
1274
+ * Both spellings are now read STRUCTURALLY, off the attribute nodes: an
1275
+ * `attribute` whose `attribute_name` is `context` with value `module`, or one
1276
+ * whose `attribute_name` is `module` with no value. No pattern literal, and a
1277
+ * single-quoted `context='module'` is now found for the same reason the
1278
+ * double-quoted one always was: by construction, not by spelling.
1279
+ */
1280
+ isModuleContextBlock(rootNode, index) {
1281
+ const blocks = [];
1282
+ const walk = (n) => {
1283
+ if (n.type === "script_element") {
1284
+ blocks.push(n);
1285
+ return;
1286
+ }
1287
+ for (let i = 0; i < n.namedChildCount; i++) {
1288
+ const c = n.namedChild(i);
1289
+ if (c) walk(c);
1290
+ }
1291
+ };
1292
+ walk(rootNode);
1293
+ const block = blocks[index];
1294
+ if (!block) return false;
1295
+ const startTag = this.findChildByType(block, "start_tag");
1296
+ if (!startTag) return false;
1297
+ for (let i = 0; i < startTag.namedChildCount; i++) {
1298
+ const attr = startTag.namedChild(i);
1299
+ if (!attr || attr.type !== "attribute") continue;
1300
+ const name = this.attributeName(attr);
1301
+ if (name === "module") return true;
1302
+ if (name === "context" && this.attributeValue(attr) === "module") return true;
1303
+ }
1304
+ return false;
1305
+ }
1306
+ /** The `attribute_value` text of an attribute, however it is quoted. */
1307
+ attributeValue(attr) {
1308
+ const value = this.findChildByType(attr, "attribute_value");
1309
+ if (value) return value.text;
1310
+ const quoted = this.findChildByType(attr, "quoted_attribute_value");
1311
+ if (!quoted) return "";
1312
+ const inner = this.findChildByType(quoted, "attribute_value");
1313
+ return inner ? inner.text : "";
1314
+ }
1315
+ isExported(exportInfos, name) {
1316
+ for (const e of exportInfos) {
1317
+ if (e.name === name && !e.isDefault) return true;
1318
+ }
1319
+ return false;
1320
+ }
1321
+ /**
1322
+ * ONTOLOGY-WORKSTREAM SVELTE3 — the LOCAL BINDING an `import` introduces, which
1323
+ * is the only name Svelte's template can legitimately write.
1324
+ *
1325
+ * The JS/TS sub-analyzer does not write bare binding names in `items`; it writes
1326
+ * the item AS SPELLED. Measured on real analyzer-local records:
1327
+ *
1328
+ * import * as Card from "./card" -> items ["* as Card"], alias "Card"
1329
+ * import { alpha as beta } from "./b" -> items ["alpha as beta"], alias undefined
1330
+ * import Foo from "./foo" -> items ["Foo"], alias undefined
1331
+ * import { qux } from "./qux" -> items ["qux"], alias undefined
1332
+ *
1333
+ * So a consumer that adds `items` to a set of binding names holds `"* as Card"`
1334
+ * and `"alpha as beta"` and never `Card` or `beta`. Two rules were reading that
1335
+ * set and both were silent on the forms above; this is the one derivation they
1336
+ * share now.
1337
+ *
1338
+ * Pure String operations — the `as` split is `indexOf`/`slice`, never a regex
1339
+ * (the lane is regex-free by contract).
1340
+ */
1341
+ importLocalBindings(imp) {
1342
+ const module = String(imp?.module ?? "");
1343
+ const out = [];
1344
+ if (imp?.isSideEffectImport === true) return out;
1345
+ const items = Array.isArray(imp?.items) ? imp.items : [];
1346
+ const alias = typeof imp?.alias === "string" ? String(imp.alias).trim() : "";
1347
+ if (alias) {
1348
+ out.push({ name: alias, isNamespace: items.some((i) => String(i).trim().startsWith("*")), module });
1349
+ return out;
1350
+ }
1351
+ for (const raw of items) {
1352
+ let item = String(raw).trim();
1353
+ if (!item) continue;
1354
+ const isNamespace = item.startsWith("*");
1355
+ if (isNamespace) {
1356
+ item = item.startsWith("* as ") ? item.slice(4).trim() : "";
1357
+ if (!item) continue;
1358
+ }
1359
+ const asIdx = item.indexOf(" as ");
1360
+ if (asIdx >= 0) item = item.slice(asIdx + 4).trim();
1361
+ if (!item) continue;
1362
+ out.push({ name: item, isNamespace, module });
1363
+ }
1364
+ return out;
1365
+ }
1366
+ /**
1367
+ * IS THIS MODULE SPECIFIER A PACKAGE THE PROJECT DOES NOT PUBLISH?
1368
+ *
1369
+ * The NEGATIVE cases are the whole rule, and each one is a shape that would otherwise be
1370
+ * claimed wrongly:
1371
+ *
1372
+ * * `.` / `..` / `/` — a FILE, not a package. That question belongs to the file claim
1373
+ * (`importProjectFile`) and is answered separately below.
1374
+ * * `$lib`, `$app`, `$env` — a framework ALIAS into the project's own tree. In this
1375
+ * corpus the app root is not the project root (`shadcn-svelte/docs/src/lib`), so the
1376
+ * analyzer cannot even spell the file it names; calling it external would be a false
1377
+ * statement that the admitted file list happens to confirm, which is the laundering
1378
+ * shape and is refused here rather than discovered later.
1379
+ * * a URI scheme (`virtual:`, `http:`, `data:`) — not a package at all.
1380
+ * * a bare name the project PUBLISHES (`projectPublishedPackages`) — the monorepo
1381
+ * self-reference, measured above at 24 import records in the `svelte` checkout.
1382
+ *
1383
+ * A single-segment specifier IS eligible, and that is deliberately wider than the shape
1384
+ * rule `sourcePathClaim`'s own docstring states for FILE targets (`"dependency"`,
1385
+ * `"SubProjectB"`): a module specifier with no separator and no extension cannot name a
1386
+ * relative file in ESM, and the thing that stops a self-reference is the published-name
1387
+ * table rather than the shape. Measured after the change: 0 of the resulting claims was
1388
+ * refuted by the project's own file list.
1389
+ */
1390
+ isExternalPackageSpecifier(spec, published) {
1391
+ if (spec === "" || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("$")) return false;
1392
+ if (spec.includes("\\") || containsWhitespace(spec)) return false;
1393
+ if (isUriSchemeSpecifier(spec)) return false;
1394
+ const segs = spec.split("/").filter((s) => s !== "");
1395
+ if (segs.length === 0) return false;
1396
+ const root = spec.startsWith("@") ? segs.slice(0, 2).join("/") : segs[0];
1397
+ if (root === "" || root === "@") return false;
1398
+ return !published.has(root);
1399
+ }
1400
+ /**
1401
+ * PUBLISH THE CLAIM THE LANE ALREADY HOLDS — the module half of an import edge, and the
1402
+ * PROVENANCE of an imported binding.
1403
+ *
1404
+ * THE DEFECT IS NOT A MISSING COMPUTATION. Three of this lane's four largest unresolved
1405
+ * shapes are edges where the answer is already computed and thrown away one line later:
1406
+ *
1407
+ * imports (616 of 616 unresolved) the edge publishes `<specifier>:<item>` and
1408
+ * `metadata.module` carries the specifier, but NO claim key is set — so a dependency
1409
+ * the project HAS (142 relative `.svelte` specifiers in this draw) and a dependency the
1410
+ * project CANNOT have (bare npm packages) both read "no answer". `externalClaimOf`
1411
+ * reads `importProjectFile` and `sourcePathClaim`; neither was ever set here.
1412
+ * instantiates / calls / uses / references a target whose ROOT binding is an import
1413
+ * from a package the project does not publish (`TrendingUpIcon` from
1414
+ * `@lucide/svelte/icons/minus`, `AccordionPrimitive.Root` from `bits-ui`) names a
1415
+ * declaration outside the population — which is precisely what `isExternal` claims,
1416
+ * and what the checkout's own package names can PROVE from provenance instead of
1417
+ * guessing from the name.
1418
+ *
1419
+ * WHAT IS DELIBERATELY NOT DONE, and this is the trap refused in CODE rather than in
1420
+ * prose: **no target is ever rebased.** `<Card.Root/>` is not rewritten to `Root` or to
1421
+ * the module's file stem `card`; `$lib/utils.js:cn` is not rewritten to `cn`. That rewrite
1422
+ * moves edges out of `unresolved` and into `collRes` — a tie-break the indexer measures
1423
+ * 37,318 of 49,729 WRONG — and the names it would land on in this corpus (`Root`,
1424
+ * `Header`, `Title`, `Trigger`, `Button`, `card`) are exactly the registry's recurring
1425
+ * ones. A claim is not a resolution: `classifyEdge` reads an external marker or a cause
1426
+ * ONLY where the target names nothing (`decls === 0`), so no edge here can become
1427
+ * `resolved` and the ambiguity columns cannot move.
1428
+ *
1429
+ * AND THE `$lib` / RELATIVE NON-FILE HALF IS LEFT ALONE ON PURPOSE. 251 `$lib` import
1430
+ * edges and the 214 component/read edges bound to `$lib` or relative modules name
1431
+ * declarations that ARE in the project (the module resolves under `src/lib`; the component
1432
+ * is `button.svelte`'s own `button` symbol). No honest claim exists for them, and the
1433
+ * module-namespace member (`<Card.Root/>`, 419 edges — this lane's top cause) has no
1434
+ * name-keyed identity at all. Both are ruled in `validation/m14/verification/SVELTE-FIX.md`
1435
+ * §2 with the file-level evidence.
1436
+ */
1437
+ publishResolutionClaims(relationships, imports, symbols, filePath, projectRoot) {
1438
+ if (!projectRoot) return;
1439
+ const published = projectPublishedPackages(projectRoot);
1440
+ const bindings = /* @__PURE__ */ new Map();
1441
+ for (const imp of imports) {
1442
+ for (const b of this.importLocalBindings(imp)) if (!bindings.has(b.name)) bindings.set(b.name, b.module);
1443
+ }
1444
+ const declaredHere = new Set(symbols.map((s) => String(s.name ?? "")));
1445
+ for (const r of relationships) {
1446
+ const md = r.metadata ?? (r.metadata = {});
1447
+ const tgt = String(r.target ?? "");
1448
+ if (String(r.type) === ASTEngine.RelationshipType.Imports) {
1449
+ const spec2 = typeof md.module === "string" ? md.module : "";
1450
+ if (spec2 === "") continue;
1451
+ if (spec2.startsWith("./") || spec2.startsWith("../")) {
1452
+ if (!spec2.endsWith(".svelte")) continue;
1453
+ const abs = path.resolve(path.dirname(filePath), spec2);
1454
+ let isFile = false;
1455
+ try {
1456
+ isFile = statSync(abs).isFile();
1457
+ } catch {
1458
+ isFile = false;
1459
+ }
1460
+ if (!isFile) continue;
1461
+ const rel = path.relative(projectRoot, abs).split(path.sep).join("/");
1462
+ if (rel === "" || rel.startsWith("..")) continue;
1463
+ md.importProjectFile = rel;
1464
+ continue;
1465
+ }
1466
+ if (this.isExternalPackageSpecifier(spec2, published)) md.sourcePathClaim = [spec2];
1467
+ continue;
1468
+ }
1469
+ if (!isDottedIdentifierChain(tgt)) continue;
1470
+ const root = tgt.includes(".") ? tgt.slice(0, tgt.indexOf(".")) : tgt;
1471
+ if (declaredHere.has(root)) continue;
1472
+ const spec = bindings.get(root);
1473
+ if (spec === void 0) continue;
1474
+ if (!this.isExternalPackageSpecifier(spec, published)) continue;
1475
+ md.isExternal = true;
1476
+ }
1477
+ }
1478
+ // ============================================================================
1479
+ // S1 TEMPLATE EXPRESSION EXTRACTION
1480
+ // ============================================================================
1481
+ /** Extract the opaque expression text from a node (svelte_raw_text child). */
1482
+ /** The `svelte_raw_text` node the expression body lives in — the node that
1483
+ * gives the expression a LINE (M59 #825). `expressionText` returns only the
1484
+ * string, which is exactly why every template read in this analyzer claimed
1485
+ * no line at all. */
1486
+ expressionTextNode(node) {
1487
+ const stack = [node];
1488
+ while (stack.length > 0) {
1489
+ const m = stack.pop();
1490
+ if (m.type === "svelte_raw_text") return m;
1491
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
1492
+ const c = m.namedChild(i);
1493
+ if (c) stack.push(c);
1494
+ }
1495
+ }
1496
+ return null;
1497
+ }
1498
+ expressionText(node) {
1499
+ return this.expressionTextNode(node)?.text ?? "";
1500
+ }
1501
+ /** Every `svelte_raw_text` node under `root`, in document order — text AND
1502
+ * position from the same node, so a fragment's line cannot drift from the
1503
+ * fragment the name was read out of. */
1504
+ rawTextNodes(root) {
1505
+ const out = [];
1506
+ const stack = [root];
1507
+ while (stack.length > 0) {
1508
+ const m = stack.pop();
1509
+ if (m.type === "svelte_raw_text") out.push(m);
1510
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
1511
+ const c = m.namedChild(i);
1512
+ if (c) stack.push(c);
1513
+ }
1514
+ }
1515
+ return out.sort((a, b) => a.startIndex - b.startIndex);
1516
+ }
1517
+ /** Line breaks before `end` — how far into a fragment a match sits. */
1518
+ static linesBefore(text, end) {
1519
+ let n = 0;
1520
+ for (let i = 0; i < end && i < text.length; i++) if (text[i] === "\n") n++;
1521
+ return n;
1522
+ }
1523
+ /**
1524
+ * Shadow-binding sites only: the names are DEFINITIONS, so no line is ever
1525
+ * claimed for them — but they MUST land in `shadowed`.
1526
+ *
1527
+ * Careful: the pre-#825 call sites did this by passing the shadow set as the
1528
+ * collector's `out` (`collectIdentifiersFromText(vars, shadowed, new Set())`),
1529
+ * which is where the bindings were registered. Now that `out` is a Map, that
1530
+ * shape silently registers nothing — measured: `{#each rows as row}` and
1531
+ * `{#await p then value}` stopped shadowing, so `row`/`item`/`total`/`value`/
1532
+ * `d` all came back as template READS (two existing fixtures caught it:
1533
+ * svelte/each-shadow-collision and svelte/await-inline-shadow). So the names
1534
+ * are collected into a scratch Map and then transferred, preserving the
1535
+ * original semantics exactly.
1536
+ */
1537
+ collectShadowNames(text, shadowed) {
1538
+ const names = /* @__PURE__ */ new Map();
1539
+ this.collectIdentifiersFromText(text, names, /* @__PURE__ */ new Set());
1540
+ for (const n of names.keys()) shadowed.add(n);
1541
+ }
1542
+ collectIdentifiersFromText(text, out, shadowed, baseLine = 0, sites) {
1543
+ const isIdentStart = (ch) => ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_" || ch === "$";
1544
+ const isIdentPart = (ch) => isIdentStart(ch) || ch >= "0" && ch <= "9";
1545
+ const isSpace = (ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r";
1546
+ const stack = [];
1547
+ const brackets = [];
1548
+ const ternary = [];
1549
+ const isObjectKey = (end) => {
1550
+ let j = end;
1551
+ while (j < text.length && isSpace(text[j])) j++;
1552
+ if (text[j] !== ":") return false;
1553
+ const top = brackets.length > 0 ? brackets[brackets.length - 1] : "";
1554
+ if (top !== "{") return false;
1555
+ const t = ternary.length > 0 ? ternary[ternary.length - 1] : false;
1556
+ return !t;
1557
+ };
1558
+ const emit = (word, start) => {
1559
+ let k = start - 1;
1560
+ while (k >= 0 && isSpace(text[k])) k--;
1561
+ const prev = k >= 0 ? text[k] : "";
1562
+ const prev2 = k - 1 >= 0 ? text[k - 1] : "";
1563
+ if (prev === "." && prev2 !== ".") return;
1564
+ if (prev2 === "?" && prev === ".") return;
1565
+ if (prev === "#") return;
1566
+ if (shadowed.has(word)) return;
1567
+ if (isObjectKey(start + word.length)) return;
1568
+ const site = baseLine + _RealSvelteAnalyzer.linesBefore(text, start);
1569
+ out.set(word, site);
1570
+ if (sites) {
1571
+ const arr = sites.get(word);
1572
+ if (arr) {
1573
+ if (arr[arr.length - 1] !== site) arr.push(site);
1574
+ } else sites.set(word, [site]);
1575
+ }
1576
+ };
1577
+ let i = 0;
1578
+ while (i < text.length) {
1579
+ const ch = text[i];
1580
+ const top = stack.length > 0 ? stack[stack.length - 1] : "";
1581
+ if (top === "'" || top === '"') {
1582
+ if (ch === "\\") {
1583
+ i += 2;
1584
+ continue;
1585
+ }
1586
+ if (ch === top) stack.pop();
1587
+ i++;
1588
+ continue;
1589
+ }
1590
+ if (top === "`") {
1591
+ if (ch === "\\") {
1592
+ i += 2;
1593
+ continue;
1594
+ }
1595
+ if (ch === "$" && text[i + 1] === "{") {
1596
+ stack.push("${");
1597
+ brackets.push("{");
1598
+ ternary.push(false);
1599
+ i += 2;
1600
+ continue;
1601
+ }
1602
+ if (ch === "`") {
1603
+ stack.pop();
1604
+ brackets.pop();
1605
+ ternary.pop();
1606
+ i++;
1607
+ continue;
1608
+ }
1609
+ i++;
1610
+ continue;
1611
+ }
1612
+ if (ch === "'" || ch === '"' || ch === "`") {
1613
+ stack.push(ch);
1614
+ i++;
1615
+ continue;
1616
+ }
1617
+ if (ch === "{") {
1618
+ stack.push(ch);
1619
+ brackets.push("{");
1620
+ ternary.push(false);
1621
+ i++;
1622
+ continue;
1623
+ }
1624
+ if (ch === "(" || ch === "[") {
1625
+ stack.push(ch);
1626
+ brackets.push(ch);
1627
+ ternary.push(false);
1628
+ i++;
1629
+ continue;
1630
+ }
1631
+ if (ch === "}") {
1632
+ if (top === "{" || top === "${") {
1633
+ stack.pop();
1634
+ brackets.pop();
1635
+ ternary.pop();
1636
+ } else if (top === "(") {
1637
+ stack.pop();
1638
+ brackets.pop();
1639
+ ternary.pop();
1640
+ }
1641
+ i++;
1642
+ continue;
1643
+ }
1644
+ if (ch === ")") {
1645
+ if (top === "(") {
1646
+ stack.pop();
1647
+ brackets.pop();
1648
+ ternary.pop();
1649
+ }
1650
+ i++;
1651
+ continue;
1652
+ }
1653
+ if (ch === "]") {
1654
+ if (top === "[") {
1655
+ stack.pop();
1656
+ brackets.pop();
1657
+ ternary.pop();
1658
+ }
1659
+ i++;
1660
+ continue;
1661
+ }
1662
+ if (ch === "?") {
1663
+ if (text[i + 1] === "?" || text[i + 1] === ".") {
1664
+ i += 2;
1665
+ continue;
1666
+ }
1667
+ if (ternary.length > 0) ternary[ternary.length - 1] = true;
1668
+ i++;
1669
+ continue;
1670
+ }
1671
+ if (ch === ":") {
1672
+ if (ternary.length > 0) ternary[ternary.length - 1] = false;
1673
+ i++;
1674
+ continue;
1675
+ }
1676
+ if (ch === "/" && (text[i + 1] === "/" || text[i + 1] === "*")) {
1677
+ if (text[i + 1] === "/") {
1678
+ const nl = text.indexOf("\n", i + 2);
1679
+ i = nl >= 0 ? nl + 1 : text.length;
1680
+ } else {
1681
+ const end = text.indexOf("*/", i + 2);
1682
+ i = end >= 0 ? end + 2 : text.length;
1683
+ }
1684
+ continue;
1685
+ }
1686
+ if (!isIdentStart(ch)) {
1687
+ i++;
1688
+ continue;
1689
+ }
1690
+ let j = i + 1;
1691
+ while (j < text.length && isIdentPart(text[j])) j++;
1692
+ emit(text.slice(i, j), i);
1693
+ i = j;
1694
+ }
1695
+ }
1696
+ /** {#each items as item, i} — collection is a use; loop vars shadow. */
1697
+ collectEachExpression(node, out, shadowed, sites) {
1698
+ const start = this.findDescendant(node, (c) => c.type === "each_start");
1699
+ if (!start) return;
1700
+ const rawTexts = this.rawTextNodes(start);
1701
+ const collection = rawTexts[0];
1702
+ if (collection) this.collectIdentifiersFromText(collection.text, out, shadowed, collection.startPosition.row + 1, sites);
1703
+ const vars = rawTexts[1];
1704
+ const varsText = vars?.text ?? "";
1705
+ const varsBase = (vars?.startPosition.row ?? 0) + 1;
1706
+ const keyClause = _RealSvelteAnalyzer.patternKeySplit(varsText);
1707
+ if (keyClause) {
1708
+ this.collectBindingPattern(keyClause.pattern, out, shadowed, varsBase, sites);
1709
+ this.collectIdentifiersFromText(
1710
+ keyClause.keyText,
1711
+ out,
1712
+ shadowed,
1713
+ varsBase + _RealSvelteAnalyzer.linesBefore(varsText, keyClause.keyAt),
1714
+ sites
1715
+ );
1716
+ } else {
1717
+ this.collectBindingPattern(varsText, out, shadowed, varsBase, sites);
1718
+ }
1719
+ }
1720
+ /** {#await promise then value} {:catch err} — promise is a use; vars shadow. */
1721
+ collectAwaitExpression(node, out, shadowed, sites) {
1722
+ const start = this.findDescendant(node, (c) => c.type === "await_start");
1723
+ const thenBlk = this.findDescendant(node, (c) => c.type === "then_start");
1724
+ const catchBlk = this.findDescendant(node, (c) => c.type === "catch_start");
1725
+ if (start) {
1726
+ const rawNodes = this.rawTextNodes(start);
1727
+ const rawTexts = rawNodes.map((r) => r.text);
1728
+ const text = rawTexts.join(" ");
1729
+ const firstLine = rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0;
1730
+ const m = splitAwaitHead(text);
1731
+ const promiseExpr = m ? m.head : text;
1732
+ this.collectIdentifiersFromText(promiseExpr, out, shadowed, firstLine);
1733
+ if (m) {
1734
+ this.collectBindingPattern(
1735
+ text.slice(m.matchLength),
1736
+ out,
1737
+ shadowed,
1738
+ firstLine + _RealSvelteAnalyzer.linesBefore(text, m.matchLength),
1739
+ sites
1740
+ );
1741
+ }
1742
+ }
1743
+ for (const blk of [thenBlk, catchBlk]) {
1744
+ if (!blk) continue;
1745
+ const rawNodes = this.rawTextNodes(blk);
1746
+ this.collectBindingPattern(
1747
+ rawNodes.map((r) => r.text).join(" "),
1748
+ out,
1749
+ shadowed,
1750
+ rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0,
1751
+ sites
1752
+ );
1753
+ }
1754
+ }
1755
+ /** D5: {#if cond} / {:else if cond} / {#key exp} — the condition is a
1756
+ * svelte_raw_text child of if_start/else_if_start/key_start (no
1757
+ * `expression` wrapper, unlike mustache bodies). Every identifier is a
1758
+ * member read; these heads bind nothing, so no shadow registration. */
1759
+ collectConditionExpression(node, out, shadowed, sites) {
1760
+ const start = this.findDescendant(node, (c) => c.type === "if_start" || c.type === "else_if_start" || c.type === "key_start");
1761
+ if (!start) return;
1762
+ const rawNodes = this.rawTextNodes(start);
1763
+ const baseLine = rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0;
1764
+ this.collectIdentifiersFromText(rawNodes.map((r) => r.text).join(" "), out, shadowed, baseLine, sites);
1765
+ }
1766
+ /**
1767
+ * {#snippet row(label, count)} heads — params are template-local.
1768
+ *
1769
+ * F28: `{#snippet counter(c = count)}` binds `c` and READS `count`, and the
1770
+ * grammar hands the parameter list over WITHOUT its parens or the snippet name
1771
+ * (measured: `snippet_start` raw text is `c = count` for exactly that head), so the
1772
+ * pattern scanner takes the fragment as-is. A parameter annotation is a type and
1773
+ * the bare `{#snippet typed(c: number = 4)}` spelling is the fixture's shape for
1774
+ * it: `number` is neither a read nor a binding.
1775
+ */
1776
+ collectSnippetParams(node, out, shadowed, sites) {
1777
+ const start = this.findDescendant(node, (c) => c.type === "snippet_start");
1778
+ if (!start) return;
1779
+ const rawNodes = this.rawTextNodes(start);
1780
+ this.collectBindingPattern(
1781
+ rawNodes.map((r) => r.text).join(" "),
1782
+ out,
1783
+ shadowed,
1784
+ rawNodes[0] ? rawNodes[0].startPosition.row + 1 : 0,
1785
+ sites
1786
+ );
1787
+ }
1788
+ /** A template expression body ({count}, label={label}, {@html raw}). */
1789
+ collectExpressionIdentifiers(node, out, shadowed, sites) {
1790
+ const textNode = this.expressionTextNode(node);
1791
+ const text = textNode?.text ?? "";
1792
+ const decl = _RealSvelteAnalyzer.declarationTagSplit(text);
1793
+ if (decl) {
1794
+ this.collectBindingPattern(decl.head, out, shadowed, textNode ? textNode.startPosition.row + 1 : 0, sites);
1795
+ this.collectIdentifiersFromText(
1796
+ decl.tail,
1797
+ out,
1798
+ shadowed,
1799
+ (textNode ? textNode.startPosition.row + 1 : 0) + _RealSvelteAnalyzer.linesBefore(text, decl.eqAt + 1),
1800
+ sites
1801
+ );
1802
+ return;
1803
+ }
1804
+ const scope = this.eventCarrierName(node.parent) ? this.withArrowParams(shadowed, text) : shadowed;
1805
+ this.collectIdentifiersFromText(text, out, scope, textNode ? textNode.startPosition.row + 1 : 0, sites);
1806
+ }
1807
+ /**
1808
+ * M60 SVELTE-D6 / F26: split a Svelte 5.36 declaration tag `{let x = …}` /
1809
+ * `{const x = …}` into its binding head and its initialiser. Returns null for
1810
+ * an ordinary mustache, which is the overwhelming majority. A leading
1811
+ * `let`/`const` is REQUIRED and so is the word boundary after it: without the
1812
+ * boundary `{letter = 5}` — an assignment to a member named `letter` — parses as
1813
+ * a `let` tag binding `ter`, a fabricated shadow rather than a real one.
1814
+ */
1815
+ static declarationTagSplit(text) {
1816
+ const ws = (i2) => {
1817
+ while (i2 < text.length && (text[i2] === " " || text[i2] === " " || text[i2] === "\n" || text[i2] === "\r")) i2++;
1818
+ return i2;
1819
+ };
1820
+ const i = ws(0);
1821
+ const kw = text.startsWith("let", i) ? "let" : text.startsWith("const", i) ? "const" : null;
1822
+ if (!kw) return null;
1823
+ const after = i + kw.length;
1824
+ const next = text[after];
1825
+ const boundary = next === "{" || next === "[" || next === " " || next === " " || next === "\n" || next === "\r";
1826
+ if (!boundary) return null;
1827
+ const body = ws(after);
1828
+ let depth = 0;
1829
+ for (let j = body; j < text.length; j++) {
1830
+ const ch = text[j];
1831
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
1832
+ else if (ch === ")" || ch === "]" || ch === "}") depth--;
1833
+ else if (ch === "=" && depth === 0) {
1834
+ if (text[j + 1] === "=" || text[j - 1] === "=" || text[j - 1] === "!" || text[j - 1] === "<" || text[j - 1] === ">") continue;
1835
+ return { head: text.slice(body, j), tail: text.slice(j + 1), eqAt: j };
1836
+ }
1837
+ }
1838
+ return null;
1839
+ }
1840
+ /**
1841
+ * F28 (2026-10-12): A BINDING PATTERN HAS READ POSITIONS AND BINDING POSITIONS.
1842
+ *
1843
+ * Every template site that writes a PATTERN — `{#each rows as …}`, `{#await p
1844
+ * then …}`, `{:catch …}`, `{#snippet name(…)}`, `{let …}` / `{@const …}` heads —
1845
+ * used to hand its text to the flat word collector, which did two wrong things at
1846
+ * once:
1847
+ *
1848
+ * a LOST READ `{#snippet counter(c = count)}` binds `c` and READS `count`;
1849
+ * a FALSE SHADOW `{#each xs as { a: b }}` binds `b` alone — `a` is a PROPERTY
1850
+ * name, so registering it silenced a member named `a` for the
1851
+ * whole block.
1852
+ *
1853
+ * Measured before this fix over the 6,439-file corpus (svelte 4,561 + shadcn-svelte
1854
+ * 1,878 git-tracked .svelte files) with the grammar-derived oracle
1855
+ * (`script/m14/tmp/svelte-uses-oracle.ts`, which re-parses each template fragment
1856
+ * with the JAVASCRIPT grammar and so decides the split from
1857
+ * `assignment_pattern.right` and `computed_property_name` rather than from a
1858
+ * character walk): 35 template reads the payload does not publish, 27 of them this
1859
+ * class, over 81 sites carrying a computed key or a default value.
1860
+ *
1861
+ * The split, and nothing wider than the split:
1862
+ * COMPUTED KEY `{ [k]: v }` the inside of `[ ]` is an EXPRESSION (a read)
1863
+ * DEFAULT VALUE `x = expr` `expr` is an EXPRESSION (a read) and `x` binds
1864
+ * PROPERTY NAME `{ a: b }` `a` is neither a read nor a binding
1865
+ * VALUE `{ a: b }` `b` binds, recursively (nested patterns too)
1866
+ * SHORTHAND `{ a }` `a` binds
1867
+ * REST `...props` `props` binds
1868
+ * ANNOTATION `c: number = 4` `number` is neither: it is a type
1869
+ * BOUND EARLIER `{ idx, [idx - 1]: last }` `idx` is a template-local by the
1870
+ * time the computed key reads it, so it publishes NOTHING
1871
+ *
1872
+ * The last rule is why the walk is ONE LEFT-TO-RIGHT PASS that mutates the shadow
1873
+ * set as it binds: a reader that collects all reads before all bindings publishes
1874
+ * a false edge on the first entry of every `{ name, [name …] }` pattern, and that
1875
+ * is the one row the fixture pins as forbidden precisely because it would silently
1876
+ * ADD edges rather than lose them.
1877
+ *
1878
+ * Pure-String scanning: this family's regex census is 0 and stays 0.
1879
+ */
1880
+ collectBindingPattern(text, out, shadowed, baseLine = 0, sites) {
1881
+ if (!text) return;
1882
+ const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
1883
+ const isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$";
1884
+ const isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9";
1885
+ const skipWs = (i, e2) => {
1886
+ while (i < e2 && isSpace(text[i])) i++;
1887
+ return i;
1888
+ };
1889
+ const identAt = (i) => {
1890
+ if (i >= text.length || !isIdentStart(text[i])) return null;
1891
+ let j = i + 1;
1892
+ while (j < text.length && isIdentPart(text[j])) j++;
1893
+ return { name: text.slice(i, j), end: j };
1894
+ };
1895
+ const read = (s2, e2) => {
1896
+ if (s2 >= e2) return;
1897
+ this.collectIdentifiersFromText(
1898
+ text.slice(s2, e2),
1899
+ out,
1900
+ shadowed,
1901
+ baseLine + _RealSvelteAnalyzer.linesBefore(text, s2),
1902
+ sites
1903
+ );
1904
+ };
1905
+ const bind = (name) => {
1906
+ if (isIdentifier(name)) shadowed.add(name);
1907
+ };
1908
+ const atTopLevel = (i, e2, ch) => {
1909
+ let d = 0;
1910
+ let j = i;
1911
+ while (j < e2) {
1912
+ const c = text[j];
1913
+ if (c === "'" || c === '"' || c === "`") {
1914
+ j = _RealSvelteAnalyzer.skipQuoted(text, j);
1915
+ continue;
1916
+ }
1917
+ if (c === "(" || c === "[" || c === "{") {
1918
+ d++;
1919
+ j++;
1920
+ continue;
1921
+ }
1922
+ if (c === ")" || c === "]" || c === "}") {
1923
+ d--;
1924
+ j++;
1925
+ continue;
1926
+ }
1927
+ if (c === ch && d === 0) {
1928
+ const prev = j > 0 ? text[j - 1] : "";
1929
+ const next = text[j + 1] ?? "";
1930
+ if (ch === "=" && (next === "=" || prev === "=" || prev === "!" || prev === "<" || prev === ">")) {
1931
+ j++;
1932
+ continue;
1933
+ }
1934
+ return j;
1935
+ }
1936
+ j++;
1937
+ }
1938
+ return -1;
1939
+ };
1940
+ const scanValue = (s0, e2) => {
1941
+ const s2 = skipWs(s0, e2);
1942
+ if (s2 >= e2) return;
1943
+ if (text.startsWith("...", s2)) {
1944
+ const w2 = identAt(skipWs(s2 + 3, e2));
1945
+ if (w2) bind(w2.name);
1946
+ return;
1947
+ }
1948
+ const ch = text[s2];
1949
+ if (ch === "{" || ch === "[") {
1950
+ const close = _RealSvelteAnalyzer.matchingBracket(text, s2);
1951
+ const end = close < 0 || close > e2 ? e2 : close;
1952
+ if (ch === "{") scanObject(s2 + 1, end);
1953
+ else scanArray(s2 + 1, end);
1954
+ return;
1955
+ }
1956
+ const w = identAt(s2);
1957
+ if (!w) {
1958
+ read(s2, e2);
1959
+ return;
1960
+ }
1961
+ let k = skipWs(w.end, e2);
1962
+ if (k < e2 && text[k] === "?" && text[k + 1] === ":") k = skipWs(k + 1, e2);
1963
+ if (k < e2 && text[k] === ":") {
1964
+ bind(w.name);
1965
+ const eq = atTopLevel(k + 1, e2, "=");
1966
+ if (eq >= 0) read(eq + 1, e2);
1967
+ return;
1968
+ }
1969
+ if (k < e2 && text[k] === "=") {
1970
+ bind(w.name);
1971
+ read(k + 1, e2);
1972
+ return;
1973
+ }
1974
+ bind(w.name);
1975
+ };
1976
+ const scanObject = (s2, e2) => {
1977
+ for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s2, e2)) {
1978
+ const i = skipWs(a, b);
1979
+ if (i >= b) continue;
1980
+ if (text.startsWith("...", i)) {
1981
+ const w2 = identAt(skipWs(i + 3, b));
1982
+ if (w2) bind(w2.name);
1983
+ continue;
1984
+ }
1985
+ const ch = text[i];
1986
+ if (ch === "[") {
1987
+ const close = _RealSvelteAnalyzer.matchingBracket(text, i);
1988
+ const end = close < 0 || close > b ? b : close;
1989
+ read(i + 1, end);
1990
+ const k2 = skipWs(end + 1, b);
1991
+ if (k2 < b && text[k2] === ":") scanValue(k2 + 1, b);
1992
+ else if (k2 < b && text[k2] === "=") read(k2 + 1, b);
1993
+ continue;
1994
+ }
1995
+ if (ch === "'" || ch === '"' || ch === "`") {
1996
+ const k2 = skipWs(_RealSvelteAnalyzer.skipQuoted(text, i), b);
1997
+ if (k2 < b && text[k2] === ":") scanValue(k2 + 1, b);
1998
+ continue;
1999
+ }
2000
+ const w = identAt(i);
2001
+ if (!w) {
2002
+ read(i, b);
2003
+ continue;
2004
+ }
2005
+ const k = skipWs(w.end, b);
2006
+ if (k < b && text[k] === ":") {
2007
+ scanValue(k + 1, b);
2008
+ continue;
2009
+ }
2010
+ if (k < b && text[k] === "=") {
2011
+ bind(w.name);
2012
+ read(k + 1, b);
2013
+ continue;
2014
+ }
2015
+ bind(w.name);
2016
+ }
2017
+ };
2018
+ const scanArray = (s2, e2) => {
2019
+ for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s2, e2)) scanValue(a, b);
2020
+ };
2021
+ let s = skipWs(0, text.length);
2022
+ let e = text.length;
2023
+ if (s < e && text[s] === "(") {
2024
+ const close = _RealSvelteAnalyzer.matchingBracket(text, s);
2025
+ if (close >= 0 && skipWs(close + 1, e) >= e) {
2026
+ s = s + 1;
2027
+ e = close;
2028
+ }
2029
+ }
2030
+ for (const [a, b] of _RealSvelteAnalyzer.splitTopLevel(text, s, e)) scanValue(a, b);
2031
+ }
2032
+ /**
2033
+ * `i` is at a quote: the index just past its closing quote (or the end of the
2034
+ * text when it never closes). A TEMPLATE literal's `${ … }` re-enters code, so its
2035
+ * braces are tracked — otherwise a `,` inside an interpolation would split a
2036
+ * parameter list. Structural scanning only; the READ side is the ordinary
2037
+ * character collector, which has its own (identical) quote machine.
2038
+ */
2039
+ static skipQuoted(text, i) {
2040
+ const q = text[i];
2041
+ let j = i + 1;
2042
+ let depth = 0;
2043
+ while (j < text.length) {
2044
+ const c = text[j];
2045
+ if (c === "\\") {
2046
+ j += 2;
2047
+ continue;
2048
+ }
2049
+ if (q === "`") {
2050
+ if (c === "$" && text[j + 1] === "{") {
2051
+ depth++;
2052
+ j += 2;
2053
+ continue;
2054
+ }
2055
+ if (c === "}" && depth > 0) {
2056
+ depth--;
2057
+ j++;
2058
+ continue;
2059
+ }
2060
+ }
2061
+ if (c === q && depth === 0) return j + 1;
2062
+ j++;
2063
+ }
2064
+ return text.length;
2065
+ }
2066
+ /** Index of the bracket matching the opener at `open`, or -1 when unbalanced. */
2067
+ static matchingBracket(text, open) {
2068
+ const closerOf = (c) => c === "(" ? ")" : c === "[" ? "]" : c === "{" ? "}" : "";
2069
+ const stack = [];
2070
+ let i = open;
2071
+ while (i < text.length) {
2072
+ const ch = text[i];
2073
+ if (ch === "'" || ch === '"' || ch === "`") {
2074
+ i = _RealSvelteAnalyzer.skipQuoted(text, i);
2075
+ continue;
2076
+ }
2077
+ const expect = closerOf(ch);
2078
+ if (expect) {
2079
+ stack.push(expect);
2080
+ i++;
2081
+ continue;
2082
+ }
2083
+ if (ch === ")" || ch === "]" || ch === "}") {
2084
+ if (stack.length === 0 || stack[stack.length - 1] !== ch) return -1;
2085
+ stack.pop();
2086
+ if (stack.length === 0) return i;
2087
+ }
2088
+ i++;
2089
+ }
2090
+ return -1;
2091
+ }
2092
+ /** `[s, e)` split on TOP-LEVEL commas: quotes, brackets and `${…}` are skipped whole. */
2093
+ static splitTopLevel(text, s, e) {
2094
+ const segs = [];
2095
+ let start = s;
2096
+ let i = s;
2097
+ while (i < e) {
2098
+ const ch = text[i];
2099
+ if (ch === "'" || ch === '"' || ch === "`") {
2100
+ i = _RealSvelteAnalyzer.skipQuoted(text, i);
2101
+ continue;
2102
+ }
2103
+ if (ch === "(" || ch === "[" || ch === "{") {
2104
+ const close = _RealSvelteAnalyzer.matchingBracket(text, i);
2105
+ i = close < 0 || close >= e ? e : close + 1;
2106
+ continue;
2107
+ }
2108
+ if (ch === ",") {
2109
+ segs.push([start, i]);
2110
+ start = i + 1;
2111
+ i++;
2112
+ continue;
2113
+ }
2114
+ i++;
2115
+ }
2116
+ segs.push([start, e]);
2117
+ return segs;
2118
+ }
2119
+ /**
2120
+ * F25/F28: split a loop binding list's trailing KEY CLAUSE off its pattern —
2121
+ * `row (keyOf(row))` gives the pattern `row ` and the key `keyOf(row)`.
2122
+ *
2123
+ * The grammar keeps the key INSIDE the same fragment as the bindings
2124
+ * (`{#each rows as row}` gives `row`, `{#each rows as row (row.id)}` gives
2125
+ * `row (row.id)`), so the two halves have to be separated by hand. The test is
2126
+ * STRUCTURAL rather than `indexOf("(")`: the key clause is the parenthesised group
2127
+ * that ends the list, which keeps a `(` inside a pattern's own default
2128
+ * (`{ [k]: v = f() }`) from being mistaken for it.
2129
+ */
2130
+ static patternKeySplit(text) {
2131
+ let e = text.length;
2132
+ while (e > 0 && (text[e - 1] === " " || text[e - 1] === " " || text[e - 1] === "\n" || text[e - 1] === "\r")) e--;
2133
+ if (e === 0 || text[e - 1] !== ")") return null;
2134
+ let depth = 0;
2135
+ let openAt = -1;
2136
+ for (let i = 0; i < e; i++) {
2137
+ const ch = text[i];
2138
+ if (ch === "'" || ch === '"' || ch === "`") {
2139
+ i = _RealSvelteAnalyzer.skipQuoted(text, i) - 1;
2140
+ continue;
2141
+ }
2142
+ if (ch === "(" || ch === "[" || ch === "{") {
2143
+ if (depth === 0 && ch === "(") openAt = i;
2144
+ depth++;
2145
+ continue;
2146
+ }
2147
+ if (ch === ")" || ch === "]" || ch === "}") {
2148
+ depth--;
2149
+ if (depth === 0) {
2150
+ if (i === e - 1 && openAt >= 0) {
2151
+ return { pattern: text.slice(0, openAt), keyText: text.slice(openAt + 1, e - 1), keyAt: openAt + 1 };
2152
+ }
2153
+ openAt = -1;
2154
+ }
2155
+ }
2156
+ }
2157
+ return null;
2158
+ }
2159
+ /**
2160
+ * F29: record the TEMPLATE read a dedupe drop would otherwise lose.
2161
+ *
2162
+ * Called only from the graph dedupe, for a row that is being DROPPED in favour of a
2163
+ * row already published for the same `type|source|target`. It does nothing unless
2164
+ * the dropped row is a template read and the survivor is not one — the case the
2165
+ * fixture measures — and it writes onto the SURVIVOR:
2166
+ *
2167
+ * `isTemplateRead: true` "this row stands for a template read too"
2168
+ * `templateReadSites: [ … ]` WHICH sites, merged with any already recorded
2169
+ *
2170
+ * The site list is written even for a SINGLE site, unlike the read map's
2171
+ * `templateReadSites` convention (present only when a member is read on more than
2172
+ * one line). The two are not inconsistent, and the difference is what
2173
+ * `line_number` means on each: on a template-read row `line_number` IS a site, so a
2174
+ * one-element list adds nothing; here `line_number` is the SCRIPT's line, so for a
2175
+ * single dropped site the list is the only place that site exists. The divergence
2176
+ * is recorded in `archive/docs-pre-consolidation/analyzers-BRANCH-INVENTORY-2026-10-07.md` rather than smoothed
2177
+ * over. Returns whether anything was recorded, so a caller could assert the empty
2178
+ * case.
2179
+ */
2180
+ static recordDroppedTemplateRead(survivor, dropped) {
2181
+ if (dropped.metadata?.isTemplateExpression !== true) return false;
2182
+ if (survivor.metadata?.isTemplateExpression === true) return false;
2183
+ const sites = new Set(survivor.metadata?.templateReadSites ?? []);
2184
+ for (const s of dropped.metadata?.templateReadSites ?? []) {
2185
+ if (typeof s === "number") sites.add(s);
2186
+ }
2187
+ if (typeof dropped.line_number === "number") sites.add(dropped.line_number);
2188
+ if (sites.size === 0) return false;
2189
+ survivor.metadata = {
2190
+ ...survivor.metadata ?? {},
2191
+ isTemplateRead: true,
2192
+ templateReadSites: [...sites].sort((a, b) => a - b)
2193
+ };
2194
+ return true;
2195
+ }
2196
+ /**
2197
+ * F23: is this attribute one of the two EVENT CARRIERS — the two spellings that
2198
+ * make their value a function Svelte calls, and therefore the only two whose
2199
+ * arrow parameters bind?
2200
+ *
2201
+ * `eventCarrierName` is deliberately narrower than "an attribute whose name
2202
+ * starts with on": `onclick` is an event only on an INTRINSIC tag (on a
2203
+ * component it is a prop, and the parent attribute node does not carry the tag
2204
+ * this walk already resolved), so the caller's own `on:`/`onclick` decision is
2205
+ * what this reads. A bare `on:click={save}` has no arrow at all and
2206
+ * `arrowParams` returns nothing, which is correct: a bare identifier is a call
2207
+ * target and is handled by `onHandlerCall`.
2208
+ */
2209
+ eventCarrierName(parent) {
2210
+ if (!parent || parent.type !== "attribute") return null;
2211
+ const name = this.attributeName(parent);
2212
+ if (!name) return null;
2213
+ if (name.startsWith("on:")) return name;
2214
+ return isOnEventAttrName(name) && name.length > 4 ? name : null;
2215
+ }
2216
+ /** `shadowed` widened by the parameters written at the head of `text`. */
2217
+ withArrowParams(shadowed, text) {
2218
+ const params = _RealSvelteAnalyzer.arrowParams(text);
2219
+ if (params.length === 0) return shadowed;
2220
+ const out = new Set(shadowed);
2221
+ for (const p of params) out.add(p);
2222
+ return out;
2223
+ }
2224
+ /**
2225
+ * F23: the bound names of an arrow function's parameters, read off the text
2226
+ * before the FIRST top-level `=>`. Pure-String scanning, no regex — this
2227
+ * family's regex census is 0 and stays 0.
2228
+ *
2229
+ * `(evt) =>` gives `evt`; `() =>` gives nothing; `async ({ data }) =>` gives
2230
+ * `data`. Inside a DESTRUCTURING pattern an identifier immediately after a `:`
2231
+ * is the rename KEY (`{ a: local }` binds `local`, not `a`) and one inside
2232
+ * `[ ]` is a COMPUTED key, which is a read rather than a binding — both are
2233
+ * skipped rather than over-shadowed, because over-shadowing silently drops a
2234
+ * true read instead of publishing a false one.
2235
+ */
2236
+ static arrowParams(text) {
2237
+ const out = [];
2238
+ let depth = 0;
2239
+ let arrow = -1;
2240
+ for (let i = 0; i < text.length; i++) {
2241
+ const c = text[i];
2242
+ if (c === "(" || c === "[" || c === "{") depth++;
2243
+ else if (c === ")" || c === "]" || c === "}") depth--;
2244
+ else if (depth === 0 && c === "=" && text[i + 1] === ">") {
2245
+ arrow = i;
2246
+ break;
2247
+ } else if (c === ";") break;
2248
+ }
2249
+ if (arrow < 0) return out;
2250
+ let head = text.slice(0, arrow).trim();
2251
+ if (head.startsWith("async")) head = head.slice(5).trim();
2252
+ if (head.startsWith("(") && head.endsWith(")")) head = head.slice(1, -1);
2253
+ else if (!isIdentifier(head)) return out;
2254
+ const isWordChar = (ch) => ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
2255
+ const binding = (raw) => {
2256
+ let t = raw.trim();
2257
+ if (!t) return;
2258
+ if (t.startsWith("...")) t = t.slice(3).trim();
2259
+ if (!t) return;
2260
+ const destructured = t.indexOf("{") >= 0 || t.indexOf("[") >= 0;
2261
+ if (!destructured) {
2262
+ let end = t.length;
2263
+ for (let i2 = 0; i2 < t.length; i2++) {
2264
+ if (t[i2] === "=" || t[i2] === ":") {
2265
+ end = i2;
2266
+ break;
2267
+ }
2268
+ }
2269
+ const w = t.slice(0, end).trim();
2270
+ if (isIdentifier(w)) out.push(w);
2271
+ return;
2272
+ }
2273
+ let bracket = 0;
2274
+ let i = 0;
2275
+ while (i < t.length) {
2276
+ const c = t[i];
2277
+ if (c === "[") {
2278
+ bracket++;
2279
+ i++;
2280
+ continue;
2281
+ }
2282
+ if (c === "]") {
2283
+ if (bracket > 0) bracket--;
2284
+ i++;
2285
+ continue;
2286
+ }
2287
+ if (bracket > 0 || !isWordChar(c) || c >= "0" && c <= "9") {
2288
+ i++;
2289
+ continue;
2290
+ }
2291
+ let j = i;
2292
+ while (j < t.length && isWordChar(t[j])) j++;
2293
+ const w = t.slice(i, j);
2294
+ let k = i - 1;
2295
+ while (k >= 0 && (t[k] === " " || t[k] === " ")) k--;
2296
+ if (!(t[k] === ":")) out.push(w);
2297
+ i = j;
2298
+ }
2299
+ };
2300
+ let d = 0;
2301
+ let token = "";
2302
+ for (const ch of head) {
2303
+ if (ch === "(" || ch === "[" || ch === "{") {
2304
+ d++;
2305
+ token += ch;
2306
+ continue;
2307
+ }
2308
+ if (ch === ")" || ch === "]" || ch === "}") {
2309
+ d--;
2310
+ if (d < 0) break;
2311
+ token += ch;
2312
+ if (d === 0) binding(token);
2313
+ token = "";
2314
+ continue;
2315
+ }
2316
+ if (d === 0 && ch === ",") {
2317
+ binding(token);
2318
+ token = "";
2319
+ continue;
2320
+ }
2321
+ token += ch;
2322
+ }
2323
+ binding(token);
2324
+ return [...new Set(out)];
2325
+ }
2326
+ /** D4: last all-identifier segment of a dotted render target
2327
+ * (`ns.row(it)` -> row; `a . b . c()` -> c). Returns undefined unless
2328
+ * every segment is a bare identifier — the caller keeps its fallback. */
2329
+ dottedRenderLeaf(text) {
2330
+ let body = text.trim();
2331
+ if (body.endsWith("}")) body = body.slice(0, -1);
2332
+ const paren = body.indexOf("(");
2333
+ if (paren >= 0) body = body.slice(0, paren);
2334
+ const parts = body.split(".").map((p) => {
2335
+ let q = p.trim();
2336
+ if (q.endsWith("?")) q = q.slice(0, -1).trim();
2337
+ return q;
2338
+ });
2339
+ if (parts.length < 2 || !parts.every((p) => isIdentifier(p))) return void 0;
2340
+ return parts[parts.length - 1];
2341
+ }
2342
+ /** {@html raw} / {@const total = expr} / {@debug expr} — body is a direct
2343
+ * svelte_raw_text child of the tag (no `expression` wrapper). */
2344
+ collectTagExpressionIdentifiers(node, out, shadowed, sites) {
2345
+ const textNode = this.expressionTextNode(node);
2346
+ const baseLine = textNode ? textNode.startPosition.row + 1 : 0;
2347
+ let text = textNode?.text ?? "";
2348
+ let base = baseLine;
2349
+ if (node.type === "const_tag") {
2350
+ const eq = this.topLevelEq(text);
2351
+ if (eq >= 0) {
2352
+ this.collectBindingPattern(text.slice(0, eq), out, shadowed, baseLine, sites);
2353
+ base = baseLine + _RealSvelteAnalyzer.linesBefore(text, eq + 1);
2354
+ text = text.slice(eq + 1);
2355
+ }
2356
+ }
2357
+ this.collectIdentifiersFromText(text, out, shadowed, base, sites);
2358
+ }
2359
+ /** Index of the first top-level `=` (not inside () / [] / {}) in `text`.*/
2360
+ topLevelEq(text) {
2361
+ let depth = 0;
2362
+ for (let i = 0; i < text.length; i++) {
2363
+ const c = text[i];
2364
+ if (c === "(" || c === "[" || c === "{") depth++;
2365
+ else if (c === ")" || c === "]" || c === "}") depth--;
2366
+ else if (c === "=" && depth === 0) return i;
2367
+ }
2368
+ return -1;
2369
+ }
2370
+ /**
2371
+ * M59 (#825) follow-up, measured on shadcn-svelte forms: the handler's
2372
+ * NAME and its LINE travel together. The old text-only leaf plus the
2373
+ * expression node's line split them across lines for multiline arrow
2374
+ * bodies. Same three rules as before (first call target, bare identifier,
2375
+ * dotted callable leaf), each now carrying its offset into the raw text.
2376
+ */
2377
+ onHandlerCall(attr) {
2378
+ const value = this.findChildByType(attr, "expression");
2379
+ if (!value) return void 0;
2380
+ let text = "";
2381
+ let baseLine = 0;
2382
+ const stack = [value];
2383
+ while (stack.length > 0) {
2384
+ const m = stack.pop();
2385
+ if (m.type === "svelte_raw_text") {
2386
+ text = m.text;
2387
+ baseLine = m.startPosition.row + 1;
2388
+ break;
2389
+ }
2390
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
2391
+ const c = m.namedChild(i);
2392
+ if (c) stack.push(c);
2393
+ }
2394
+ }
2395
+ if (!text) return void 0;
2396
+ const at = (offset) => baseLine + _RealSvelteAnalyzer.linesBefore(text, offset);
2397
+ for (let i = 0; i < text.length; i++) {
2398
+ const ch = text[i];
2399
+ if (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$") {
2400
+ let end = i;
2401
+ while (end < text.length) {
2402
+ const c = text[end];
2403
+ if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "$") end++;
2404
+ else break;
2405
+ }
2406
+ const ident = text.slice(i, end);
2407
+ if (end < text.length && text[end] === "(") {
2408
+ let back = i - 1;
2409
+ while (back >= 0 && (text[back] === " " || text[back] === " " || text[back] === "\n")) back--;
2410
+ if (back < 0 || text[back] !== ".") return { name: ident, line: at(i) };
2411
+ }
2412
+ i = end;
2413
+ }
2414
+ }
2415
+ if (isIdentifier(text) && !NON_CALLABLE_LITERALS.has(text)) return { name: text, line: at(0) };
2416
+ const parts = text.split(".");
2417
+ if (parts.length > 1 && parts.every((p) => isIdentifier(p))) {
2418
+ const leaf = parts[parts.length - 1];
2419
+ if (NON_CALLABLE_LITERALS.has(leaf)) return void 0;
2420
+ return { name: leaf, line: at(text.lastIndexOf(leaf)) };
2421
+ }
2422
+ return void 0;
2423
+ }
2424
+ /**
2425
+ * #419-D - evidence-tier classification for EVERY emitted relationship.
2426
+ * Same rules as the Vue/JVM family. Template RENDERS are
2427
+ * usage_contextual (component usage by tag name); the delegate's script `new`
2428
+ * `instantiates` rows keep their structural tier.
2429
+ */
2430
+ classifyEvidenceTier(relationships) {
2431
+ for (const r of relationships) {
2432
+ const md = r.metadata ?? (r.metadata = {});
2433
+ if (md.evidenceClass) continue;
2434
+ const t = String(r.type ?? r.relationshipType ?? "");
2435
+ const conf = typeof r.confidence === "number" ? r.confidence : 1;
2436
+ if (t === "contains" || t === "exports") {
2437
+ md.evidenceClass = "structural";
2438
+ continue;
2439
+ }
2440
+ if (t === "references") {
2441
+ md.evidenceClass = md.isImport ? "import_based" : "reference_qualified";
2442
+ continue;
2443
+ }
2444
+ if (t === "extends" || t === "implements" || t === "overrides") {
2445
+ md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
2446
+ continue;
2447
+ }
2448
+ if (t === "instantiates") {
2449
+ md.evidenceClass = md.isComponentUsage ? "usage_contextual" : "structural";
2450
+ continue;
2451
+ }
2452
+ md.evidenceClass = "usage_contextual";
2453
+ }
2454
+ }
2455
+ findAncestor(node, predicate) {
2456
+ let current = node.parent;
2457
+ while (current) {
2458
+ if (predicate(current)) return current;
2459
+ current = current.parent;
2460
+ }
2461
+ return null;
2462
+ }
2463
+ findDescendant(node, predicate) {
2464
+ const stack = [node];
2465
+ while (stack.length > 0) {
2466
+ const n = stack.pop();
2467
+ if (!(n.startIndex === node.startIndex && n.endIndex === node.endIndex) && predicate(n)) return n;
2468
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
2469
+ const c = n.namedChild(i);
2470
+ if (c) stack.push(c);
2471
+ }
2472
+ }
2473
+ return null;
2474
+ }
2475
+ elementTagName(el) {
2476
+ return this.elementTagNameNode(el)?.text;
2477
+ }
2478
+ /** The `tag_name` node itself — the node that SPELLS the tag (M59 #825: the
2479
+ * Instantiates edge for a component usage is about that name, so its line
2480
+ * must come from the tag, not from the element's opening bracket). */
2481
+ elementTagNameNode(el) {
2482
+ const startTag = this.findChildByType(el, "start_tag") || this.findChildByType(el, "self_closing_tag");
2483
+ if (startTag) {
2484
+ const tag = this.findChildByType(startTag, "tag_name");
2485
+ if (tag) return tag;
2486
+ }
2487
+ return this.findChildByType(el, "tag_name");
2488
+ }
2489
+ attributeName(attr) {
2490
+ const name = this.findChildByType(attr, "attribute_name");
2491
+ return name ? name.text : "";
2492
+ }
2493
+ offsetRange(range, lineOffset, colOffset, indexOffset = 0) {
2494
+ const out = { ...range };
2495
+ const firstLine = out.startLine === 1;
2496
+ out.startLine += lineOffset;
2497
+ out.endLine += lineOffset;
2498
+ if (firstLine && colOffset) {
2499
+ out.startColumn += colOffset;
2500
+ if (out.endLine === out.startLine) out.endColumn += colOffset;
2501
+ }
2502
+ if (indexOffset && out.startIndex !== void 0) out.startIndex += indexOffset;
2503
+ if (indexOffset && out.endIndex !== void 0) out.endIndex += indexOffset;
2504
+ return out;
2505
+ }
2506
+ findChildByType(node, type) {
2507
+ for (let i = 0; i < node.namedChildCount; i++) {
2508
+ const c = node.namedChild(i);
2509
+ if (c && c.type === type) return c;
2510
+ }
2511
+ return null;
2512
+ }
2513
+ nodeToRange(node) {
2514
+ return {
2515
+ startLine: node.startPosition.row + 1,
2516
+ endLine: node.endPosition.row + 1,
2517
+ // Columns are 1-based everywhere else in the codebase (+1); this
2518
+ // analyzer silently emitted 0-based columns.
2519
+ startColumn: node.startPosition.column + 1,
2520
+ endColumn: node.endPosition.column + 1,
2521
+ startIndex: node.startIndex,
2522
+ endIndex: node.endIndex
2523
+ };
2524
+ }
2525
+ };
2526
+ export {
2527
+ RealSvelteAnalyzer,
2528
+ containsWhitespace,
2529
+ isDottedIdentifierChain,
2530
+ isUriSchemeSpecifier,
2531
+ splitAwaitHead
2532
+ };