@backbencher/brain 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-KY7K552E.js +17 -0
  8. package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-2BSKD7O3.js +40 -0
  11. package/dist/chunk-2DE27DVC.js +1314 -0
  12. package/dist/chunk-2RRBMNBQ.js +322 -0
  13. package/dist/chunk-2RTN2DFU.js +711 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3LFTSMIV.js +174 -0
  17. package/dist/chunk-47HKX4AJ.js +930 -0
  18. package/dist/chunk-4H22TLOT.js +543 -0
  19. package/dist/chunk-5SD45FLC.js +818 -0
  20. package/dist/chunk-66A5ZFYA.js +1758 -0
  21. package/dist/chunk-6AIRWAO2.js +247 -0
  22. package/dist/chunk-6WNGD3NN.js +196 -0
  23. package/dist/chunk-7AN7U5IZ.js +371 -0
  24. package/dist/chunk-BHB7BYYI.js +626 -0
  25. package/dist/chunk-BQ3OVQ3P.js +125 -0
  26. package/dist/chunk-CW4XXSNN.js +1479 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DYM4FQEE.js +90 -0
  30. package/dist/chunk-EWMWK7WN.js +75 -0
  31. package/dist/chunk-FVVQTSIM.js +3427 -0
  32. package/dist/chunk-FWSL4VG6.js +1290 -0
  33. package/dist/chunk-GBI7V7KY.js +69 -0
  34. package/dist/chunk-GEFFO6O2.js +152 -0
  35. package/dist/chunk-HOWAQXJO.js +236 -0
  36. package/dist/chunk-HQSPJDHI.js +613 -0
  37. package/dist/chunk-IFZNR5UJ.js +2863 -0
  38. package/dist/chunk-J4PMBXQQ.js +336 -0
  39. package/dist/chunk-KAET47ZL.js +125 -0
  40. package/dist/chunk-KCDECRJY.js +440 -0
  41. package/dist/chunk-KLK3PBTO.js +1119 -0
  42. package/dist/chunk-L5TDYKYK.js +76 -0
  43. package/dist/chunk-MLKGABMK.js +9 -0
  44. package/dist/chunk-OWISBAOF.js +61 -0
  45. package/dist/chunk-PJTNGD3Z.js +6924 -0
  46. package/dist/chunk-QLK6ED4Q.js +235 -0
  47. package/dist/chunk-R2GW4F6R.js +19 -0
  48. package/dist/chunk-RCMTFSEF.js +87 -0
  49. package/dist/chunk-RH7KC5HU.js +12050 -0
  50. package/dist/chunk-RM3PY6TG.js +376 -0
  51. package/dist/chunk-ROMM6KMC.js +433 -0
  52. package/dist/chunk-SK6YVHFZ.js +211 -0
  53. package/dist/chunk-SM7RFQRO.js +214 -0
  54. package/dist/chunk-UFE77L67.js +782 -0
  55. package/dist/chunk-UGR4QIGS.js +67 -0
  56. package/dist/chunk-UUNHHX4M.js +13 -0
  57. package/dist/chunk-V44PGPA6.js +2104 -0
  58. package/dist/chunk-VL52PL2T.js +71 -0
  59. package/dist/chunk-VURYFIHW.js +201 -0
  60. package/dist/chunk-XJKJYIPP.js +7436 -0
  61. package/dist/chunk-YI6FF2ZR.js +340 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-642F7E5V.js +593 -0
  65. package/dist/database-TCTZDLE5.js +9 -0
  66. package/dist/doctor-GXCKSJOX.js +395 -0
  67. package/dist/edit-engine-KM7MPFEC.js +51 -0
  68. package/dist/engine-42TZFNIZ.js +11 -0
  69. package/dist/engine-LO6L4JLQ.js +18 -0
  70. package/dist/fingerprint-XDXFRWO6.js +33 -0
  71. package/dist/framework-intelligence-DWFCKV44.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-PL5YA7BA.js +21 -0
  74. package/dist/graph_request-MM4REP6H.js +350 -0
  75. package/dist/impact_request-WDE47IZ5.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-6BD7UBHR.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-4PW3AFXP.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-ZS5TS2Z6.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-DGZBIZS7.js +355 -0
  90. package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
  91. package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
  92. package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
  93. package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
  94. package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
  95. package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
  96. package/dist/real-css-analyzer-HPU277Q6.js +15 -0
  97. package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
  98. package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
  99. package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
  100. package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
  101. package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
  102. package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
  103. package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
  104. package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
  105. package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
  106. package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
  107. package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
  108. package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
  109. package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
  110. package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
  111. package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
  112. package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
  113. package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
  114. package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
  115. package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
  116. package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
  117. package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
  118. package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
  119. package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
  120. package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
  121. package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
  122. package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
  123. package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
  124. package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
  125. package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
  126. package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
  127. package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
  128. package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
  129. package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
  130. package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
  131. package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
  132. package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
  133. package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
  134. package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
  135. package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
  136. package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
  137. package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
  138. package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
  139. package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
  140. package/dist/regex-search-OPTLKUN5.js +239 -0
  141. package/dist/rules-I453JTRC.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-Y5GB5KXA.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-KB4MU4SI.js +16 -0
  146. package/dist/taint-YOTN5X55.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-G4NAGJIH.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-RF675A3C.js +32 -0
  151. package/dist/type-inference-4Y7WTMFE.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-LXGNFO37.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,725 @@
1
+ import {
2
+ ASTEngine
3
+ } from "./chunk-V44PGPA6.js";
4
+ import "./chunk-BQ3OVQ3P.js";
5
+ import "./chunk-SK6YVHFZ.js";
6
+ import "./chunk-2BSKD7O3.js";
7
+ import "./chunk-KAET47ZL.js";
8
+ import "./chunk-KCDECRJY.js";
9
+ import "./chunk-MLKGABMK.js";
10
+
11
+ // src/ast/real-hcl-analyzer.ts
12
+ var TYPE_NAME_BLOCKS = /* @__PURE__ */ new Set(["resource", "data"]);
13
+ var SINGLE_LABEL_BLOCKS = /* @__PURE__ */ new Set(["variable", "output", "module", "provider"]);
14
+ var TOP_LEVEL_KINDS = {
15
+ resource: ASTEngine.SymbolKind.Class,
16
+ data: ASTEngine.SymbolKind.Class,
17
+ variable: ASTEngine.SymbolKind.Variable,
18
+ output: ASTEngine.SymbolKind.Variable,
19
+ module: ASTEngine.SymbolKind.Module,
20
+ provider: ASTEngine.SymbolKind.Module,
21
+ terraform: ASTEngine.SymbolKind.Module,
22
+ locals: ASTEngine.SymbolKind.Module
23
+ };
24
+ var TERRAFORM_TOP_LEVEL = new Set(Object.keys(TOP_LEVEL_KINDS));
25
+ var TERRAFRAMEWORK_NAME = "Terraform";
26
+ var BUILTIN_ROOTS = /* @__PURE__ */ new Set(["count", "each", "self", "path", "terraform"]);
27
+ var FUNCTION_DECL_BLOCKS = /* @__PURE__ */ new Set(["function"]);
28
+ var TARGET_BLOCK = "target";
29
+ var INHERITS_ATTRIBUTE = "inherits";
30
+ var OVERRIDE_BLOCK_KIND = {
31
+ override_resource: "resource",
32
+ override_data: "data",
33
+ override_module: "module"
34
+ };
35
+ var RealHclAnalyzer = class {
36
+ /** Per-analysis memo for `subtreeSize`, keyed by tree-sitter node id. */
37
+ sizeCache = /* @__PURE__ */ new Map();
38
+ analyze(rootNode, content, filePath) {
39
+ this.sizeCache.clear();
40
+ const symbols = [];
41
+ const relationships = [];
42
+ const imports = [];
43
+ const exports = [];
44
+ const errors = [];
45
+ const relKeys = /* @__PURE__ */ new Set();
46
+ const addRelationship = (source, target, type, lineNumber, metadata = {}) => {
47
+ const key = `${source}\0${type}\0${target}\0${lineNumber}`;
48
+ if (relKeys.has(key)) return;
49
+ relKeys.add(key);
50
+ if (!metadata.evidenceClass) {
51
+ metadata.evidenceClass = type === ASTEngine.RelationshipType.Contains ? "structural" : type === ASTEngine.RelationshipType.Imports ? "import_based" : type === ASTEngine.RelationshipType.Uses ? "builtin_qualified" : type === ASTEngine.RelationshipType.References ? "reference_qualified" : "structural";
52
+ }
53
+ relationships.push({ source, target, type, confidence: 1, line_number: lineNumber, metadata });
54
+ };
55
+ const terraformBlocks = /* @__PURE__ */ new Map();
56
+ const rootName = this.documentName(filePath);
57
+ const rootRange = this.nodeToRange(rootNode);
58
+ symbols.push({
59
+ name: rootName,
60
+ qualifiedName: rootName,
61
+ kind: ASTEngine.SymbolKind.Module,
62
+ range: rootRange,
63
+ scope: "",
64
+ visibility: "public",
65
+ metadata: { hclRoot: true, hclLanguage: "hcl" }
66
+ });
67
+ exports.push({ name: rootName, kind: ASTEngine.SymbolKind.Module, range: rootRange, visibility: "public" });
68
+ const functionDecls = /* @__PURE__ */ new Map();
69
+ const pendingCalls = [];
70
+ const targetDecls = /* @__PURE__ */ new Map();
71
+ const pendingInherits = [];
72
+ const stack = [
73
+ {
74
+ node: rootNode,
75
+ blockPath: [],
76
+ blockKind: void 0,
77
+ taken: /* @__PURE__ */ new Map([[rootName, 1]]),
78
+ iterators: /* @__PURE__ */ new Set(),
79
+ shadowed: /* @__PURE__ */ new Map(),
80
+ overrideKind: null,
81
+ enclosingBlockType: ""
82
+ }
83
+ ];
84
+ const pushChildren = (node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType) => {
85
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
86
+ const child = node.namedChild(i);
87
+ if (child) stack.push({ node: child, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType });
88
+ }
89
+ };
90
+ while (stack.length > 0) {
91
+ const { node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType } = stack.pop();
92
+ const type = node.type;
93
+ if (type === "block") {
94
+ let blockTypeIndex = -1;
95
+ for (let i = 0; i < node.namedChildCount; i++) {
96
+ if (node.namedChild(i)?.type === "identifier") {
97
+ blockTypeIndex = i;
98
+ break;
99
+ }
100
+ }
101
+ const blockType = blockTypeIndex >= 0 ? node.namedChild(blockTypeIndex).text : "";
102
+ const labels = [];
103
+ for (let i = 0; i < node.namedChildCount; i++) {
104
+ if (i === blockTypeIndex) continue;
105
+ const child = node.namedChild(i);
106
+ if (!child) continue;
107
+ if (child.type === "string_lit") labels.push(this.readStringLit(child));
108
+ else if (child.type === "identifier") labels.push(child.text);
109
+ }
110
+ if (!blockType) {
111
+ pushChildren(node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType);
112
+ continue;
113
+ }
114
+ const address = this.blockAddress(blockType, labels);
115
+ const name = this.disambiguate(address, taken);
116
+ const kind = TOP_LEVEL_KINDS[blockType] ?? ASTEngine.SymbolKind.Class;
117
+ const qualified = [...blockPath, name].join(".");
118
+ const ownerPath = blockPath.length > 0 ? blockPath.join(".") : rootName;
119
+ const ownerLeaf = blockPath.length > 0 ? blockPath[blockPath.length - 1] : rootName;
120
+ if (TERRAFORM_TOP_LEVEL.has(blockType)) {
121
+ terraformBlocks.set(blockType, (terraformBlocks.get(blockType) ?? 0) + 1);
122
+ }
123
+ const isFunctionDecl = FUNCTION_DECL_BLOCKS.has(blockType);
124
+ if (isFunctionDecl && labels.length > 0 && !functionDecls.has(labels[0])) {
125
+ functionDecls.set(labels[0], name);
126
+ }
127
+ if (blockType === TARGET_BLOCK && labels.length > 0 && !targetDecls.has(labels[0])) {
128
+ targetDecls.set(labels[0], address);
129
+ }
130
+ symbols.push({
131
+ name,
132
+ qualifiedName: qualified,
133
+ kind,
134
+ complexity: this.subtreeSize(node),
135
+ range: this.nodeToRange(node),
136
+ scope: ownerPath,
137
+ visibility: "public",
138
+ ownerName: ownerLeaf,
139
+ ownerKind: blockPath.length > 0 ? blockKind : ASTEngine.SymbolKind.Module,
140
+ metadata: {
141
+ hclBlockType: blockType,
142
+ hclLabels: labels,
143
+ hclBlockKind: TERRAFORM_TOP_LEVEL.has(blockType) ? blockType : "nested",
144
+ hclAddress: address,
145
+ // Ordinal among the entities sharing this address in this scope,
146
+ // where the document root occupies slot 0 when its name collides
147
+ // with the block's own address (`moved.tf` holding `moved {}`).
148
+ hclDuplicateIndex: name === address ? 0 : (taken.get(address) ?? 1) - 1,
149
+ ...isFunctionDecl ? { hclFunctionDecl: true } : {}
150
+ }
151
+ });
152
+ addRelationship(ownerPath, qualified, ASTEngine.RelationshipType.Contains, node.startPosition.row + 1, {
153
+ hclSite: "block"
154
+ });
155
+ if (blockType === "resource" || blockType === "data") {
156
+ const providerType = labels[0];
157
+ if (providerType) {
158
+ addRelationship(rootName, providerType, ASTEngine.RelationshipType.Instantiates, node.startPosition.row + 1, {
159
+ hclSite: "provider-type",
160
+ instanceType: providerType,
161
+ instanceKind: blockType,
162
+ instanceName: labels[1] ?? "",
163
+ evidenceClass: "structural"
164
+ });
165
+ }
166
+ }
167
+ if (blockType === "module") {
168
+ const source = this.findModuleSource(node);
169
+ imports.push({
170
+ module: labels[0] ?? address,
171
+ path: source,
172
+ range: this.nodeToRange(node)
173
+ });
174
+ if (source) {
175
+ addRelationship(qualified, source, ASTEngine.RelationshipType.Imports, this.findModuleSourceLine(node) ?? node.startPosition.row + 1, {
176
+ hclSite: "module-source"
177
+ });
178
+ }
179
+ }
180
+ if (blockType === "include") {
181
+ const part = this.findIncludePath(node);
182
+ if (part) {
183
+ addRelationship(ownerPath, part.symbol, ASTEngine.RelationshipType.PartOf, part.line, {
184
+ hclSite: "terragrunt-include",
185
+ // MANDATORY on every `part_of` row (types.ts): the direction is the
186
+ // directive's own — THIS file carries the included file as a part.
187
+ partRole: "library",
188
+ partPath: part.text,
189
+ partSymbol: part.symbol,
190
+ isHclIncludePart: true,
191
+ evidenceClass: "include_literal"
192
+ });
193
+ }
194
+ }
195
+ let childIterators = iterators;
196
+ let childShadowed = shadowed;
197
+ if (blockType === "dynamic") {
198
+ const found = /* @__PURE__ */ new Set();
199
+ this.findDynamicIterators(node, labels, found);
200
+ if (found.size > 0) {
201
+ childIterators = new Set(iterators);
202
+ childShadowed = new Map(shadowed);
203
+ for (const it of found) {
204
+ childIterators.add(it);
205
+ childShadowed.set(it, (childShadowed.get(it) ?? 0) + 1);
206
+ }
207
+ }
208
+ }
209
+ pushChildren(node, [...blockPath, name], kind, /* @__PURE__ */ new Map(), childIterators, childShadowed, OVERRIDE_BLOCK_KIND[blockType] ?? null, blockType);
210
+ continue;
211
+ }
212
+ if (type === "attribute") {
213
+ const keyNode = this.findChild(node, (n) => n.type === "identifier");
214
+ const key = keyNode?.text ?? "";
215
+ if (key) {
216
+ const ownerPath = blockPath.length > 0 ? blockPath.join(".") : rootName;
217
+ const ownerLeaf = blockPath.length > 0 ? blockPath[blockPath.length - 1] : rootName;
218
+ const name = blockPath.length === 0 && key === rootName ? `${key}#2` : key;
219
+ const qualified = [...blockPath, name].join(".") || name;
220
+ const expr = this.findChild(node, (n) => n.type === "expression");
221
+ const valueNode = expr ? expr.namedChild(0) : void 0;
222
+ if (enclosingBlockType === TARGET_BLOCK && key === INHERITS_ATTRIBUTE) {
223
+ for (const parent of this.readStringList(expr)) {
224
+ pendingInherits.push({ parent, line: keyNode.startPosition.row + 1, owner: ownerPath });
225
+ }
226
+ }
227
+ symbols.push({
228
+ name,
229
+ qualifiedName: qualified,
230
+ kind: ASTEngine.SymbolKind.Property,
231
+ complexity: this.subtreeSize(node),
232
+ range: this.nodeToRange(node),
233
+ scope: ownerPath,
234
+ visibility: "public",
235
+ ownerName: ownerLeaf,
236
+ ownerKind: blockPath.length > 0 ? blockKind : ASTEngine.SymbolKind.Module,
237
+ metadata: { hclValueKind: this.readValueKind(expr), hclAddress: key }
238
+ });
239
+ addRelationship(ownerPath, qualified, ASTEngine.RelationshipType.Contains, keyNode.startPosition.row + 1, {
240
+ hclSite: "attribute"
241
+ });
242
+ if (expr) {
243
+ const refs = [];
244
+ this.extractReferences(expr, iterators, shadowed, refs, pendingCalls, ownerPath);
245
+ if (overrideKind !== null && key === "target") {
246
+ for (const ref of refs) {
247
+ if (BUILTIN_ROOTS.has(ref.path.split(".")[0])) continue;
248
+ addRelationship(ownerPath, ref.path, ASTEngine.RelationshipType.Overrides, keyNode.startPosition.row + 1, {
249
+ hclSite: "override-target",
250
+ hclOverrideKind: overrideKind,
251
+ evidenceClass: "structural"
252
+ });
253
+ }
254
+ } else {
255
+ for (const ref of refs) {
256
+ const root = ref.path.split(".")[0];
257
+ const builtin = BUILTIN_ROOTS.has(root);
258
+ addRelationship(
259
+ ownerPath,
260
+ ref.path,
261
+ builtin ? ASTEngine.RelationshipType.Uses : ASTEngine.RelationshipType.References,
262
+ ref.line,
263
+ {
264
+ hclSite: builtin ? "builtin-use" : "reference",
265
+ ...builtin ? { hclBuiltin: true, languageNamespace: [...BUILTIN_ROOTS] } : {}
266
+ }
267
+ );
268
+ }
269
+ }
270
+ }
271
+ }
272
+ continue;
273
+ }
274
+ pushChildren(node, blockPath, blockKind, taken, iterators, shadowed, overrideKind, enclosingBlockType);
275
+ }
276
+ for (const site of pendingInherits) {
277
+ const declared = targetDecls.get(site.parent);
278
+ if (!declared) continue;
279
+ addRelationship(site.owner, declared, ASTEngine.RelationshipType.Extends, site.line, {
280
+ hclSite: "inherits",
281
+ // The spelling AT the inheritance site, kept because the target is the parent
282
+ // DECLARATION's address (`target._common`) rather than the bare label `_common`.
283
+ hclInheritedTarget: site.parent,
284
+ evidenceClass: "structural"
285
+ });
286
+ }
287
+ for (const site of pendingCalls) {
288
+ const declared = functionDecls.get(site.callee);
289
+ if (!declared) continue;
290
+ addRelationship(site.owner, declared, ASTEngine.RelationshipType.Calls, site.line, {
291
+ hclSite: "function-call",
292
+ // The spelling AT the call site, kept because the target is the
293
+ // declaration's index identity (`function.bindir`) rather than the bare
294
+ // name the file writes.
295
+ hclCallee: site.callee,
296
+ evidenceClass: "structural"
297
+ });
298
+ }
299
+ for (const s of symbols) {
300
+ if (s.metadata?.hclRoot === true) continue;
301
+ if (s.scope === rootName) {
302
+ exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: "public" });
303
+ addRelationship(rootName, s.name, ASTEngine.RelationshipType.Exports, s.range.startLine, {
304
+ hclSite: "export"
305
+ });
306
+ }
307
+ }
308
+ return {
309
+ language: "hcl",
310
+ symbols,
311
+ relationships,
312
+ imports,
313
+ exports,
314
+ errors,
315
+ metadata: {
316
+ complexity: symbols.length,
317
+ linesOfCode: content.split("\n").length,
318
+ patterns: [],
319
+ framework: "HCL/Terraform",
320
+ // The M60.R8 three-key family contract, and all three agree:
321
+ // `framework` names the language's host ecosystem, `detectedFrameworks`
322
+ // is ALWAYS an array (`[]` when nothing was detected) and
323
+ // `frameworkPatterns` is aligned with it by `framework` name. Before this
324
+ // the analyzer published only `framework`, which the gate records as
325
+ // `missing-contract-key` and a consumer joining the two arrays by name
326
+ // cannot do.
327
+ //
328
+ // The detection is structural, not an extension guess: HCL is the host
329
+ // language of several distinct ecosystems, and the corpus proves they
330
+ // need telling apart — consul's agent config and packer's
331
+ // `.pkr.hcl` templates are HCL with no Terraform block in sight, while
332
+ // `terraform@db4eef4` is Terraform throughout. `data`, `variable`,
333
+ // `output`, `module`, `provider`, `terraform` and `locals` are the
334
+ // Terraform top-level block set; a document declaring none of them is
335
+ // published as HCL with an empty detection list rather than being
336
+ // claimed as Terraform on the strength of its extension.
337
+ detectedFrameworks: terraformBlocks.size > 0 ? [TERRAFRAMEWORK_NAME] : [],
338
+ frameworkPatterns: terraformBlocks.size > 0 ? [
339
+ {
340
+ framework: TERRAFRAMEWORK_NAME,
341
+ patterns: [...terraformBlocks.entries()].sort((a, b) => a[0] < b[0] ? -1 : 1).map(([blockType, count]) => ({ blockType, count }))
342
+ }
343
+ ] : [],
344
+ hasFrameworkIntelligence: true,
345
+ hasTypeInference: false
346
+ }
347
+ };
348
+ }
349
+ /**
350
+ * Terraform's native address for a block, with a `blockType` fallback so a
351
+ * label-less `data {}` / `resource "x" {}` never produces an empty name.
352
+ */
353
+ blockAddress(blockType, labels) {
354
+ if (TYPE_NAME_BLOCKS.has(blockType)) {
355
+ if (labels.length >= 2) return `${labels[0]}.${labels[1]}`;
356
+ if (labels.length === 1) return `${labels[0]}`;
357
+ return blockType;
358
+ }
359
+ if (SINGLE_LABEL_BLOCKS.has(blockType)) {
360
+ return labels[0] ?? blockType;
361
+ }
362
+ return labels.length > 0 ? `${blockType}.${labels.join(".")}` : blockType;
363
+ }
364
+ /**
365
+ * Make a leaf name unique among its siblings. Terraform repeats `locals`,
366
+ * `moved`, `removed`, `import` and aliased `provider` blocks by design, and
367
+ * the HashiCorp DSLs repeat label-less blocks (waypoint's `parameter`).
368
+ * The first use keeps the bare address; later siblings get `#2`, `#3`, …
369
+ */
370
+ disambiguate(address, taken) {
371
+ const seen = taken.get(address) ?? 0;
372
+ taken.set(address, seen + 1);
373
+ if (seen === 0) return address;
374
+ let n = seen + 1;
375
+ let candidate = `${address}#${n}`;
376
+ while (taken.has(candidate)) {
377
+ n += 1;
378
+ candidate = `${address}#${n}`;
379
+ }
380
+ taken.set(candidate, 1);
381
+ return candidate;
382
+ }
383
+ /**
384
+ * The grammar node type that actually describes the value — unwrapping the
385
+ * `operation` container so a comparison is reported as `binary_operation`
386
+ * rather than as a node type that names no value kind.
387
+ */
388
+ readValueKind(expr) {
389
+ if (!expr) return "missing_expression";
390
+ const value = expr.namedChild(0);
391
+ if (!value) return "empty_expression";
392
+ if (value.type === "operation") {
393
+ const inner = value.namedChild(0);
394
+ return inner ? inner.type : "operation";
395
+ }
396
+ return value.type;
397
+ }
398
+ /**
399
+ * Walk an expression subtree and collect dotted reference paths with the line
400
+ * each name is written on.
401
+ *
402
+ * A path is a maximal run of `variable_expr` (base identifier) followed by
403
+ * `get_attr` accessors. The grammar places that run as a set of SIBLINGS under
404
+ * whatever node owns the expression — `expression` for a plain value, but
405
+ * `binary_operation` / `unary_operation` under an `operation` wrapper for
406
+ * arithmetic and comparison, and `expression` again inside each
407
+ * `template_interpolation`. So the run is read from ANY node's children, and
408
+ * a parent that is itself part of a run is not re-read.
409
+ *
410
+ * `index` / `splat` / `attr_splat` / `full_splat` end the run without
411
+ * contributing a segment: Terraform resolves `aws_instance.web[*].id` and
412
+ * `var.list[0]` against the address accumulated so far, so publishing
413
+ * `aws_instance.web` / `var.list` is the edge Terraform itself means.
414
+ *
415
+ * Real Terraform references are always qualified (`var.x`, `aws_instance.web.id`),
416
+ * so a bare single-identifier path is a type-constraint keyword (`type = string`)
417
+ * or a function name — not a reference — and is skipped. `loopVars` carries
418
+ * `for`-intro identifiers and `dynamic` iterators so block-locals are never
419
+ * mistaken for module/resource references.
420
+ *
421
+ * `callSites` collects every `function_call` CALLEE with the line it is
422
+ * written on, and nothing decides here whether it becomes an edge: a callee is
423
+ * only callable if a `function` block declares it, and that map is complete
424
+ * only after the DFS finishes (a call may precede its declaration). So the
425
+ * candidate is recorded and resolved by the caller.
426
+ */
427
+ extractReferences(node, loopVars, shadowed, out, callSites, owner) {
428
+ const type = node.type;
429
+ if (type === "function_call") {
430
+ const callee = this.findChild(node, (n) => n.type === "identifier");
431
+ if (callee) callSites.push({ callee: callee.text, line: callee.startPosition.row + 1, owner });
432
+ }
433
+ if (type === "for_expr" || type === "for_tuple_expr" || type === "for_object_expr") {
434
+ const childLoopVars = new Set(loopVars);
435
+ for (let i = 0; i < node.namedChildCount; i++) {
436
+ const child = node.namedChild(i);
437
+ if (child?.type !== "for_intro") continue;
438
+ for (let j = 0; j < child.namedChildCount; j++) {
439
+ const intro = child.namedChild(j);
440
+ if (intro?.type === "identifier") childLoopVars.add(intro.text);
441
+ }
442
+ }
443
+ for (let i = 0; i < node.namedChildCount; i++) {
444
+ const child = node.namedChild(i);
445
+ if (child) this.extractReferences(child, childLoopVars, shadowed, out, callSites, owner);
446
+ }
447
+ return;
448
+ }
449
+ if (type === "object_elem") {
450
+ const val = node.childForFieldName?.("val");
451
+ if (val) this.extractReferences(val, loopVars, shadowed, out, callSites, owner);
452
+ return;
453
+ }
454
+ for (let i = 0; i < node.childCount; i++) {
455
+ const child = node.child(i);
456
+ if (!child) continue;
457
+ if (child.type !== "variable_expr" && child.type !== "get_attr") {
458
+ if (child.isNamed) this.extractReferences(child, loopVars, shadowed, out, callSites, owner);
459
+ continue;
460
+ }
461
+ const parts = [];
462
+ const nameLines = [];
463
+ let j = i;
464
+ for (; j < node.childCount; j++) {
465
+ const c = node.child(j);
466
+ if (!c || c.type !== "variable_expr" && c.type !== "get_attr") break;
467
+ const id = this.findChild(c, (n) => n.type === "identifier");
468
+ if (!id) break;
469
+ parts.push(id.text);
470
+ nameLines.push(id.startPosition.row + 1);
471
+ }
472
+ if (parts.length >= 2 && !loopVars.has(parts[0])) {
473
+ out.push({ path: parts.join("."), line: nameLines[0] });
474
+ }
475
+ i = j - 1;
476
+ }
477
+ }
478
+ /**
479
+ * The loop local(s) a `dynamic` block binds: the `iterator` attribute's value
480
+ * when present, otherwise the block's own first label — Terraform's documented
481
+ * default, so `dynamic "egress" { content { x = egress.cidr } }` reads a
482
+ * block-local, not a project symbol. Without the label fallback the emitter
483
+ * published a false `egress.cidr` (caught by hcl/dynamic-iterator-scope).
484
+ */
485
+ findDynamicIterators(blockNode, labels, out) {
486
+ const body = this.findChild(blockNode, (n) => n.type === "body");
487
+ if (!body) return;
488
+ for (let i = 0; i < body.namedChildCount; i++) {
489
+ const attr = body.namedChild(i);
490
+ if (attr?.type !== "attribute") continue;
491
+ const key = this.findChild(attr, (n) => n.type === "identifier");
492
+ if (key?.text !== "iterator") continue;
493
+ const expr = this.findChild(attr, (n) => n.type === "expression");
494
+ const ve = expr?.namedChild(0);
495
+ if (ve?.type !== "variable_expr") continue;
496
+ const id = this.findChild(ve, (n) => n.type === "identifier");
497
+ if (id) {
498
+ out.add(id.text);
499
+ return;
500
+ }
501
+ }
502
+ if (labels.length > 0) out.add(labels[0]);
503
+ }
504
+ /** Read a `string_lit` label/value without source-text regex (grammar field). */
505
+ readStringLit(node) {
506
+ const literal = this.findChild(node, (n) => n.type === "template_literal");
507
+ if (literal) return literal.text;
508
+ return node.text;
509
+ }
510
+ /**
511
+ * The string LITERALS of a value, in source order: `["_common"]` yields one, `["binary",
512
+ * "_platforms"]` yields two, and a bare `"x"` yields one. Only `string_lit` counts — an
513
+ * interpolated `quoted_template` names a COMPUTED value, not a declaration, and resolving it
514
+ * would be a guess dressed as an edge.
515
+ */
516
+ readStringList(expr) {
517
+ if (!expr) return [];
518
+ const out = [];
519
+ const visit = (n) => {
520
+ if (n.type === "string_lit") {
521
+ out.push(this.readStringLit(n));
522
+ return;
523
+ }
524
+ for (let i = 0; i < n.namedChildCount; i++) {
525
+ const c = n.namedChild(i);
526
+ if (c) visit(c);
527
+ }
528
+ };
529
+ visit(expr);
530
+ return out;
531
+ }
532
+ /** Find a module block's `source` attribute value (unquoted via grammar field). */
533
+ findModuleSource(blockNode) {
534
+ const body = this.findChild(blockNode, (n) => n.type === "body");
535
+ if (!body) return void 0;
536
+ for (let i = 0; i < body.namedChildCount; i++) {
537
+ const attr = body.namedChild(i);
538
+ if (attr?.type !== "attribute") continue;
539
+ const key = this.findChild(attr, (n) => n.type === "identifier");
540
+ if (key?.text !== "source") continue;
541
+ const expr = this.findChild(attr, (n) => n.type === "expression");
542
+ if (!expr) return void 0;
543
+ const literal = this.findDescendant(expr, (n) => n.type === "template_literal");
544
+ if (literal) return literal.text;
545
+ return expr.text;
546
+ }
547
+ return void 0;
548
+ }
549
+ /** The line a module block's `source = …` key is written on. */
550
+ findModuleSourceLine(blockNode) {
551
+ const body = this.findChild(blockNode, (n) => n.type === "body");
552
+ if (!body) return null;
553
+ for (let i = 0; i < body.namedChildCount; i++) {
554
+ const attr = body.namedChild(i);
555
+ if (attr?.type !== "attribute") continue;
556
+ const key = this.findChild(attr, (n) => n.type === "identifier");
557
+ if (key?.text !== "source") continue;
558
+ return key.startPosition.row + 1;
559
+ }
560
+ return null;
561
+ }
562
+ /** The document's root name, from the file stem (JSON/YAML convention). */
563
+ documentName(filePath) {
564
+ const slash = filePath.lastIndexOf("/");
565
+ const base = slash >= 0 ? filePath.slice(slash + 1) : filePath;
566
+ const lower = base.toLowerCase();
567
+ for (const suffix of [".tfvars", ".tofu", ".hcl", ".tf"]) {
568
+ if (lower.endsWith(suffix)) {
569
+ const stem = base.slice(0, base.length - suffix.length);
570
+ return stem.length > 0 ? stem : base;
571
+ }
572
+ }
573
+ return base.length > 0 ? base : "hcl";
574
+ }
575
+ /**
576
+ * A terragrunt `include` block's `path`, ONLY when it is a single STRING
577
+ * LITERAL — `{ text, symbol, line }`, or null when the block names nothing
578
+ * this parse can know.
579
+ *
580
+ * WHAT IS REFUSED AND WHY. A computed value is not a file: `find_in_parent_folders()`
581
+ * is a function call whose result depends on the working directory tree, and
582
+ * `"${get_terragrunt_dir()}/root.hcl"` is an interpolated template whose static
583
+ * `root.hcl` half is one segment of a computed path. Publishing either would be a
584
+ * `part_of` whose target is an expression — an edge to nothing — so both publish
585
+ * NOTHING rather than a guess. The guard is structural (node types), not a
586
+ * spelling test: any interpolation, call, variable read, index, arithmetic or
587
+ * conditional anywhere in the value marks it computed.
588
+ *
589
+ * The SYMBOL is the included file's root-module symbol spelling: the basename's
590
+ * stem, with the same extension suffixes `documentName()` strips — because the
591
+ * arbiter resolves a target string against symbol names, and every HCL file's
592
+ * root symbol is named from its stem (`root.hcl` → `root`). The path itself is
593
+ * returned separately for `metadata.partPath`.
594
+ */
595
+ findIncludePath(blockNode) {
596
+ const body = this.findChild(blockNode, (n) => n.type === "body");
597
+ if (!body) return null;
598
+ for (let i = 0; i < body.namedChildCount; i++) {
599
+ const attr = body.namedChild(i);
600
+ if (attr?.type !== "attribute") continue;
601
+ const key = this.findChild(attr, (n) => n.type === "identifier");
602
+ if (key?.text !== "path") continue;
603
+ const expr = this.findChild(attr, (n) => n.type === "expression");
604
+ if (!expr) return null;
605
+ if (this.expressionIsComputed(expr)) return null;
606
+ const literal = this.findDescendant(expr, (n) => n.type === "template_literal");
607
+ if (!literal) return null;
608
+ const text = literal.text;
609
+ const symbol = this.includePartSymbol(text);
610
+ if (!symbol) return null;
611
+ return { text, symbol, line: key.startPosition.row + 1 };
612
+ }
613
+ return null;
614
+ }
615
+ /**
616
+ * True when the value is a runtime expression rather than a literal this parse
617
+ * can name. Kept as a node-type walk (never source text): the grammar's own
618
+ * vocabulary is the contract, so a future construct cannot slip past a spelling
619
+ * test.
620
+ */
621
+ expressionIsComputed(expr) {
622
+ const COMPUTED = /* @__PURE__ */ new Set([
623
+ "template_interpolation",
624
+ "function_call",
625
+ "variable_expr",
626
+ "get_attr",
627
+ "index",
628
+ "legacy_index",
629
+ "splat",
630
+ "attr_splat",
631
+ "full_splat",
632
+ "for_expr",
633
+ "conditional",
634
+ "binary_operation",
635
+ "unary_operation"
636
+ ]);
637
+ let computed = false;
638
+ const visit = (n) => {
639
+ if (computed || !n) return;
640
+ if (COMPUTED.has(n.type)) {
641
+ computed = true;
642
+ return;
643
+ }
644
+ for (let i = 0; i < n.namedChildCount; i++) visit(n.namedChild(i));
645
+ };
646
+ visit(expr);
647
+ return computed;
648
+ }
649
+ /**
650
+ * The symbol-shaped target for an included file's path: its basename's stem.
651
+ * `./lib/root.hcl` → `root`, `root.hcl` → `root`, `x` → `x`.
652
+ *
653
+ * A name carrying characters that can only come from an expression or a glob
654
+ * (`$`, braces, quotes, `*`, `?`, shell metacharacters) is refused: the
655
+ * `expressionIsComputed` walk already excludes interpolations, so this is the
656
+ * second guard that keeps a path-shaped or glob-shaped string out of the target
657
+ * slot. Empty is refused for the same reason a label-less block names nothing.
658
+ */
659
+ includePartSymbol(pathText) {
660
+ const slash = pathText.lastIndexOf("/");
661
+ const base = slash >= 0 ? pathText.slice(slash + 1) : pathText;
662
+ if (base.length === 0) return "";
663
+ for (let i = 0; i < base.length; i++) {
664
+ const c = base.charCodeAt(i);
665
+ if (c <= 32 || c === 34 || c === 39 || c === 96 || c === 36 || c === 40 || c === 41 || c === 123 || c === 125 || c === 91 || c === 93 || c === 42 || c === 63 || c === 59 || c === 124 || c === 38 || c === 60 || c === 62 || c === 92 || c === 126) {
666
+ return "";
667
+ }
668
+ }
669
+ const lower = base.toLowerCase();
670
+ for (const suffix of [".tfvars", ".tofu", ".hcl", ".tf"]) {
671
+ if (lower.endsWith(suffix)) {
672
+ const stem = base.slice(0, base.length - suffix.length);
673
+ return stem.length > 0 ? stem : "";
674
+ }
675
+ }
676
+ return base;
677
+ }
678
+ /**
679
+ * Nodes in a symbol's own subtree. Memoised on node id so the whole analysis
680
+ * stays O(nodes): the DFS already visits every node once, and a block's count
681
+ * deliberately includes its nested blocks — that is what makes "the biggest
682
+ * declaration in this file" a comparable number.
683
+ */
684
+ subtreeSize(node) {
685
+ const cached = this.sizeCache.get(node.id);
686
+ if (cached !== void 0) return cached;
687
+ let total = 1;
688
+ for (let i = 0; i < node.namedChildCount; i++) {
689
+ const child = node.namedChild(i);
690
+ if (child) total += this.subtreeSize(child);
691
+ }
692
+ this.sizeCache.set(node.id, total);
693
+ return total;
694
+ }
695
+ nodeToRange(node) {
696
+ return {
697
+ startLine: node.startPosition.row + 1,
698
+ startColumn: node.startPosition.column + 1,
699
+ endLine: node.endPosition.row + 1,
700
+ endColumn: node.endPosition.column + 1,
701
+ startIndex: node.startIndex,
702
+ endIndex: node.endIndex
703
+ };
704
+ }
705
+ findChild(node, predicate) {
706
+ for (let i = 0; i < node.namedChildCount; i++) {
707
+ const child = node.namedChild(i);
708
+ if (child && predicate(child)) return child;
709
+ }
710
+ return null;
711
+ }
712
+ findDescendant(node, predicate) {
713
+ for (let i = 0; i < node.namedChildCount; i++) {
714
+ const child = node.namedChild(i);
715
+ if (!child) continue;
716
+ if (predicate(child)) return child;
717
+ const found = this.findDescendant(child, predicate);
718
+ if (found) return found;
719
+ }
720
+ return null;
721
+ }
722
+ };
723
+ export {
724
+ RealHclAnalyzer
725
+ };