@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,4126 @@
1
+ import {
2
+ ASTEngine
3
+ } from "./chunk-3ZTOHHSW.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-go-analyzer.ts
12
+ function stringIncludes(str, val) {
13
+ if (!str) return false;
14
+ return str.indexOf(val) !== -1;
15
+ }
16
+ function inArray(arr, val) {
17
+ if (!arr) return false;
18
+ for (let i = 0; i < arr.length; i++) {
19
+ if (arr[i] === val) return true;
20
+ }
21
+ return false;
22
+ }
23
+ function filterSymbols(symbols, predicate) {
24
+ const result = [];
25
+ if (!symbols) return result;
26
+ for (let i = 0; i < symbols.length; i++) {
27
+ if (predicate(symbols[i])) result.push(symbols[i]);
28
+ }
29
+ return result;
30
+ }
31
+ function mapSymbols(arr, mapper) {
32
+ const result = [];
33
+ if (!arr) return result;
34
+ for (let i = 0; i < arr.length; i++) {
35
+ result.push(mapper(arr[i]));
36
+ }
37
+ return result;
38
+ }
39
+ function someSymbol(arr, predicate) {
40
+ if (!arr) return false;
41
+ for (let i = 0; i < arr.length; i++) {
42
+ if (predicate(arr[i])) return true;
43
+ }
44
+ return false;
45
+ }
46
+ function findSymbol(arr, predicate) {
47
+ if (!arr) return void 0;
48
+ for (let i = 0; i < arr.length; i++) {
49
+ if (predicate(arr[i])) return arr[i];
50
+ }
51
+ return void 0;
52
+ }
53
+ function forEachSymbol(arr, fn) {
54
+ if (!arr) return;
55
+ for (let i = 0; i < arr.length; i++) {
56
+ fn(arr[i]);
57
+ }
58
+ }
59
+ function getObjectKeysLength(obj) {
60
+ if (!obj) return 0;
61
+ let count = 0;
62
+ for (const key in obj) {
63
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
64
+ count++;
65
+ }
66
+ }
67
+ return count;
68
+ }
69
+ var GO_SUB_UNIT_REASON = {
70
+ // The callee's LEAF name is recorded; the receiver or package that owns it is
71
+ // not, and a leaf is a per-file-analyzer limit rather than a property of the
72
+ // call. Measured endpoint resolution: 19.4% of `calls` targets bind to a
73
+ // symbol in the same file.
74
+ "calls|go_function_call": "callee_recorded_as_a_leaf_name_whose_owner_is_not_resolved_per_file",
75
+ "calls|go_package_scope_call": "callee_recorded_as_a_leaf_name_whose_owner_is_not_resolved_per_file",
76
+ // A selector read names the FIELD leaf. Whether the receiver is the declared
77
+ // type, an embedded one, or an interface value is not resolved here.
78
+ "uses|go_selector_use": "field_leaf_read_whose_receiver_type_is_not_resolved_per_file",
79
+ // Channel send/receive records the channel's leaf name; the direction is in
80
+ // `operationType` and the element type is not read.
81
+ "uses|go_channel_operation": "channel_leaf_read_without_its_element_type",
82
+ // A declared/return type position records the type's leaf. Measured: 32.0% of
83
+ // `references` targets bind within the file.
84
+ "references|declared_type": "declared_type_leaf_whose_declaring_file_is_out_of_scope",
85
+ "references|return_type": "declared_type_leaf_whose_declaring_file_is_out_of_scope",
86
+ // `struct { E }` promotes E's method set. Go has no inheritance declaration,
87
+ // so the edge records a promotion that the compiler resolves, not an
88
+ // extends-clause the author wrote. THE STORED TYPE IS `mixes_in` because that
89
+ // same fact IS the mixin contract — composition of E's IMPLEMENTATION into
90
+ // T's method set, no single base, no inheritance clause. The
91
+ // `go_struct_embedding` construct id is KEPT: metadata identifies the
92
+ // construct, so the label move is 1:1 and this reason still states the fact.
93
+ "mixes_in|go_struct_embedding": "promotion_by_embedding_not_a_declared_supertype",
94
+ "extends|go_interface_embedding": "interface_method_set_inclusion_not_a_declared_supertype",
95
+ // The target is an import PATH (`net/http`, `encoding/json`), which names a
96
+ // module and not any symbol this analyzer can bind.
97
+ "imports|*": "target_is_a_module_path_not_a_symbol",
98
+ // A composite literal's type is a real declared type, read from the tree.
99
+ "instantiates|go_composite_literal_construction": "type_read_from_a_composite_literal_whose_declaration_may_be_in_another_file",
100
+ // PascalCase naming is Go's constructor CONVENTION with no declaration behind
101
+ // it. Measured false-positive class: `instantiates bitSizeError -> Itoa` at
102
+ // src/strconv/number.go:285 is `strconv.Itoa`, a formatting function whose
103
+ // name merely starts upper-case.
104
+ "instantiates|go_instantiation": "pascal_case_name_is_a_constructor_convention_with_no_declaration",
105
+ "instantiates|go_package_scope_instantiation": "pascal_case_name_is_a_constructor_convention_with_no_declaration",
106
+ // ── THE BIND REASONS — the same eleven facts, asked of a second question.
107
+ //
108
+ // The table above answers "WHY IS THIS EDGE BELOW 1.0". The D7 wall needs a
109
+ // different answer: "WHY DID THE ARBITER NOT BIND THIS EDGE's TARGET" — and
110
+ // those are NOT the same question, because an edge can be at confidence 1.0
111
+ // (the analyzer is certain the edge is real) and still have a target the
112
+ // arbiter cannot resolve to a declaration.
113
+ //
114
+ // MEASURED on the gql-gqlgen chunk (888 files, the gate's own run at the
115
+ // pre-claim analyzer): 3,820 wall rows carried NO reasonCode, and EVERY ONE
116
+ // OF THEM WAS confidence 1.0 — so the reasonCode publication loop, which
117
+ // skips `conf >= 1.0`, CANNOT reach them, and a reasonCode stamped there
118
+ // would break this analyzer's own invariant (a confidence-1.0 row must carry
119
+ // no code — see the census at script/m14/go9-census.ts).
120
+ //
121
+ // SO THE BIND REASON IS A SECOND FIELD, `metadata.bindReason`, published on
122
+ // every edge whose target is a REFERENCE to something rather than the
123
+ // declaration itself. `contains` is excluded: its target IS the declaration
124
+ // it contains, so there is no binding question to answer.
125
+ //
126
+ // The two fields share this table on purpose. One vocabulary, two questions.
127
+ // A per-type `*` fallback makes the publication COMPLETE BY CONSTRUCTION:
128
+ // no edge of a covered type can escape a named class, which is the property
129
+ // the D7 census needs.
130
+ //
131
+ // A used identifier is a plain name; the file that declares it is not read.
132
+ "uses|identifier_usage": "used_identifier_recorded_as_a_leaf_whose_declaring_file_is_out_of_scope",
133
+ // A goroutine's call and a deferred call are calls; their owner is not
134
+ // resolved per file, exactly as for a direct call.
135
+ "uses|go_goroutine": "goroutine_call_recorded_as_a_leaf_name_whose_owner_is_not_resolved_per_file",
136
+ "uses|go_defer": "deferred_call_recorded_as_a_leaf_name_whose_owner_is_not_resolved_per_file",
137
+ // An exported name is the package's own; the edge records the name alone, so
138
+ // which of the project's same-named exports it is, is not carried.
139
+ "exports|go_exported_identifier": "exported_name_recorded_as_a_leaf_whose_owning_package_is_not_the_target",
140
+ // An implemented interface is named by its leaf; the file that declares it is
141
+ // not read here.
142
+ "implements|go_interface_implementation": "implemented_interface_recorded_as_a_leaf_whose_declaring_file_is_out_of_scope",
143
+ // A promoted method is recorded with its qualified owner (`Type.Method`);
144
+ // the file declaring that type is not read.
145
+ "overrides|go_method_promotion_shadowing": "promoted_method_recorded_with_its_qualified_owner_whose_declaring_file_is_out_of_scope",
146
+ // A qualified type reference names a package and a type; the package's own
147
+ // file is not carried on the edge.
148
+ "references|reference_qualified": "qualified_type_reference_whose_declaring_file_is_out_of_scope",
149
+ // ── THE PER-TYPE FALLBACKS: the general truth for the type, used when a
150
+ // label of that type has no specific entry above. They make the bind
151
+ // publication complete: a NEW label of a covered type still gets a named
152
+ // class instead of escaping unclassified.
153
+ "calls|*": "callee_recorded_as_a_leaf_name_whose_owner_is_not_resolved_per_file",
154
+ "uses|*": "used_name_recorded_as_a_leaf_whose_declaring_file_is_out_of_scope",
155
+ "references|*": "declared_type_leaf_whose_declaring_file_is_out_of_scope",
156
+ "exports|*": "exported_name_recorded_as_a_leaf_whose_owning_package_is_not_the_target",
157
+ "extends|*": "embedded_name_recorded_as_a_promotion_rather_than_a_declared_supertype",
158
+ "implements|*": "implemented_interface_recorded_as_a_leaf_whose_declaring_file_is_out_of_scope",
159
+ "overrides|*": "promoted_method_recorded_with_its_qualified_owner_whose_declaring_file_is_out_of_scope",
160
+ "instantiates|*": "instantiated_name_recorded_as_a_leaf_whose_declaring_file_is_out_of_scope",
161
+ "mixes_in|*": "embedded_name_recorded_as_a_promotion_rather_than_a_declared_supertype",
162
+ // The rendered component is a template SYMBOL whose template-ness is read from
163
+ // its own constructor call (tree-sitter-go has no type resolution), and the
164
+ // execution is a method call — the render relation is inferred from those two
165
+ // facts rather than declared by the language, which is why this row is 0.9.
166
+ "renders|go_template_render": "render_relation_inferred_from_an_html_template_execution_and_the_template_symbols_own_construction"
167
+ };
168
+ var GO_STDLIB_SINGLE_SEGMENT = /* @__PURE__ */ new Set([
169
+ "bufio",
170
+ "bytes",
171
+ "cmp",
172
+ "context",
173
+ "crypto",
174
+ "embed",
175
+ "encoding",
176
+ "errors",
177
+ "expvar",
178
+ "flag",
179
+ "fmt",
180
+ "hash",
181
+ "html",
182
+ "image",
183
+ "io",
184
+ "iter",
185
+ "log",
186
+ "maps",
187
+ "math",
188
+ "mime",
189
+ "net",
190
+ "os",
191
+ "path",
192
+ "plugin",
193
+ "reflect",
194
+ "regexp",
195
+ "runtime",
196
+ "slices",
197
+ "sort",
198
+ "strconv",
199
+ "strings",
200
+ "structs",
201
+ "sync",
202
+ "syscall",
203
+ "testing",
204
+ "time",
205
+ "unicode",
206
+ "unique",
207
+ "unsafe",
208
+ "weak"
209
+ ]);
210
+ var GO_STDLIB_TOP_LEVEL = /* @__PURE__ */ new Set([
211
+ "archive",
212
+ "bufio",
213
+ "bytes",
214
+ "cmp",
215
+ "compress",
216
+ "container",
217
+ "context",
218
+ "crypto",
219
+ "database",
220
+ "debug",
221
+ "embed",
222
+ "encoding",
223
+ "errors",
224
+ "expvar",
225
+ "flag",
226
+ "fmt",
227
+ "go",
228
+ "hash",
229
+ "html",
230
+ "image",
231
+ "index",
232
+ "io",
233
+ "iter",
234
+ "log",
235
+ "maps",
236
+ "math",
237
+ "mime",
238
+ "net",
239
+ "os",
240
+ "path",
241
+ "plugin",
242
+ "reflect",
243
+ "regexp",
244
+ "runtime",
245
+ "slices",
246
+ "sort",
247
+ "strconv",
248
+ "strings",
249
+ "structs",
250
+ "sync",
251
+ "syscall",
252
+ "testing",
253
+ "text",
254
+ "time",
255
+ "unicode",
256
+ "unique",
257
+ "unsafe",
258
+ "weak"
259
+ ]);
260
+ function isGoStdlibModulePath(module) {
261
+ const first = module.split("/")[0];
262
+ return first.length > 0 && GO_STDLIB_TOP_LEVEL.has(first);
263
+ }
264
+ var GO_FRAMEWORK_RULES = [
265
+ {
266
+ name: "gin",
267
+ confidence: 1,
268
+ listed: true,
269
+ matches: (p) => stringIncludes(p, "gin-gonic/gin")
270
+ },
271
+ {
272
+ name: "echo",
273
+ confidence: 1,
274
+ listed: true,
275
+ matches: (p) => stringIncludes(p, "labstack/echo")
276
+ },
277
+ {
278
+ name: "fiber",
279
+ confidence: 1,
280
+ listed: true,
281
+ matches: (p) => stringIncludes(p, "gofiber/fiber")
282
+ },
283
+ {
284
+ name: "net_http",
285
+ // The only standard-library entry, and the only row that must NOT be a
286
+ // substring test: `net/http` is a complete path, and a substring match
287
+ // would also fire on a third-party module whose path merely contains it.
288
+ confidence: 0.8,
289
+ listed: true,
290
+ matches: (p) => p === "net/http" || stringIncludes(p, "net/http/")
291
+ },
292
+ {
293
+ name: "gorilla_mux",
294
+ confidence: 1,
295
+ listed: true,
296
+ matches: (p) => stringIncludes(p, "gorilla/mux")
297
+ },
298
+ {
299
+ name: "grpc",
300
+ confidence: 1,
301
+ listed: true,
302
+ matches: (p) => stringIncludes(p, "google.golang.org/grpc")
303
+ },
304
+ {
305
+ name: "testify",
306
+ confidence: 1,
307
+ listed: true,
308
+ matches: (p) => stringIncludes(p, "stretchr/testify")
309
+ },
310
+ {
311
+ name: "gorm",
312
+ confidence: 1,
313
+ listed: true,
314
+ matches: (p) => stringIncludes(p, "gorm.io/gorm")
315
+ },
316
+ {
317
+ name: "sqlx",
318
+ confidence: 1,
319
+ listed: true,
320
+ matches: (p) => stringIncludes(p, "jmoiron/sqlx")
321
+ },
322
+ {
323
+ name: "ent",
324
+ confidence: 1,
325
+ listed: true,
326
+ matches: (p) => stringIncludes(p, "entgo.io/ent")
327
+ },
328
+ {
329
+ name: "fx",
330
+ confidence: 1,
331
+ listed: true,
332
+ matches: (p) => p === "go.uber.org/fx" || stringIncludes(p, "go.uber.org/fx/")
333
+ },
334
+ {
335
+ name: "wire",
336
+ confidence: 1,
337
+ listed: true,
338
+ matches: (p) => stringIncludes(p, "google/wire")
339
+ },
340
+ {
341
+ name: "zap",
342
+ confidence: 1,
343
+ listed: true,
344
+ matches: (p) => p === "go.uber.org/zap" || stringIncludes(p, "go.uber.org/zap/")
345
+ },
346
+ {
347
+ name: "logrus",
348
+ confidence: 1,
349
+ listed: true,
350
+ matches: (p) => stringIncludes(p, "sirupsen/logrus")
351
+ },
352
+ {
353
+ name: "golang_extended",
354
+ // Category marker, not a framework — see the table's comment.
355
+ confidence: 1,
356
+ listed: false,
357
+ matches: (p) => p === "golang.org/x" || stringIncludes(p, "golang.org/x/")
358
+ }
359
+ ];
360
+ function getUniqueStrings(arr) {
361
+ const seen = /* @__PURE__ */ Object.create(null);
362
+ const result = [];
363
+ if (!arr) return result;
364
+ for (let i = 0; i < arr.length; i++) {
365
+ if (!seen[arr[i]]) {
366
+ seen[arr[i]] = true;
367
+ result.push(arr[i]);
368
+ }
369
+ }
370
+ return result;
371
+ }
372
+ var RealGoAnalyzer = class {
373
+ /**
374
+ * the CALLEE node of each deduped call, keyed by the enclosing
375
+ * function's `startIndex` and then by the call text. The calls lane iterates
376
+ * `symbol.qualified_calls` — a deduped STRING list by design (M37 #604, GOD9)
377
+ * — so the node an edge's line comes from has to be recorded where the scan
378
+ * still holds it. Reset at the top of every `analyze()`: a `startIndex` is
379
+ * file-local, so a retained map would attribute another file's line.
380
+ */
381
+ goCallSites = /* @__PURE__ */ new Map();
382
+ /**
383
+ * The canonical signature text of each method symbol, keyed by the method
384
+ * node's `startIndex` (file-local, so it cannot collide) — see `goCallSites`
385
+ * for the same discipline. The pre-fix code re-derived it on EVERY
386
+ * comparison: `checkGoInterfaceImplementation` compares each interface method
387
+ * against every candidate method, and `collectGoPromotedMethods` re-derives it
388
+ * again in each of its two shadowing tests, so one pass over a file with I
389
+ * interfaces and C candidates rebuilt the same string O(I × C × methods) times
390
+ * — the second half of the superlinear implements cost described at the hoist
391
+ * site below. Reset per `analyze()` with the same reason: a `startIndex` is
392
+ * file-local, so a retained map would leak one file's signatures into
393
+ * another's comparison.
394
+ */
395
+ goSignatures = /* @__PURE__ */ new Map();
396
+ analyze(rootNode, content, filePath) {
397
+ const ctx = { filePath, content };
398
+ this.goCallSites = /* @__PURE__ */ new Map();
399
+ this.goSignatures = /* @__PURE__ */ new Map();
400
+ const symbols = [];
401
+ const relationships = [];
402
+ const imports = [];
403
+ const exports = [];
404
+ const errors = [];
405
+ if (rootNode.hasError) {
406
+ const stack = [rootNode];
407
+ while (stack.length > 0) {
408
+ const n = stack.pop();
409
+ if (n.type === "ERROR" || n.isMissing) {
410
+ errors.push({
411
+ message: "Brain Engine (Go): Bypassed resilient parser error node",
412
+ range: {
413
+ startLine: n.startPosition.row + 1,
414
+ startColumn: n.startPosition.column + 1,
415
+ endLine: n.endPosition.row + 1,
416
+ endColumn: n.endPosition.column + 1
417
+ },
418
+ severity: `warning`
419
+ });
420
+ }
421
+ for (let i = n.childCount - 1; i >= 0; i--) {
422
+ const child = n.child(i);
423
+ if (child) stack.push(child);
424
+ }
425
+ }
426
+ }
427
+ const packageClause = this.findGoPackageClause(rootNode);
428
+ const packageName = packageClause ? this.goPackageClauseName(packageClause) : void 0;
429
+ if (packageName && packageClause) {
430
+ symbols.push({
431
+ name: packageName,
432
+ kind: ASTEngine.SymbolKind.Module,
433
+ // The PACKAGE CLAUSE's own range, read from the node. The pre-fix code
434
+ // hand-built the range from the NAME LENGTH — `startLine: 1,
435
+ // startColumn: 1, startIndex: 0, endIndex: packageName.length + 8` —
436
+ // which is only right when the clause is the first thing in the file.
437
+ // Go puts a `//go:build` / `// +build` header ABOVE the clause, and
438
+ // 158 of the pinned corpora's 3,924 files and 2,462 of kubernetes's
439
+ // 17,848 do, so every byte of the published range pointed into the
440
+ // build-tag comment: the symbol claimed line 1, column 1, bytes 0..8.
441
+ // GO4's finding stands and is now structural rather than arithmetic —
442
+ // `nodeToRange` derives the exclusive `endIndex` and the 1-based
443
+ // `endColumn` from the node's own end position, and for a clause at
444
+ // byte 0 those are byte-identical to the old hand-built values
445
+ // (`endIndex: len+8`, `endColumn: len+9`), so the common case is
446
+ // unchanged output and the header case becomes correct.
447
+ range: this.nodeToRange(packageClause),
448
+ visibility: "public",
449
+ metadata: {
450
+ nodeType: "package_clause",
451
+ isGoPackage: true
452
+ }
453
+ });
454
+ }
455
+ this.extractGoSymbols(rootNode, symbols);
456
+ this.processGoImports(rootNode, imports);
457
+ const stdlibQualifiers = this.collectGoStdlibQualifiers(imports);
458
+ this.analyzeGoRelationships(rootNode, relationships, symbols, ctx, stdlibQualifiers);
459
+ this.analyzeGoTemplateRenders(rootNode, imports, relationships);
460
+ let invalidImportPathCount = 0;
461
+ for (const imp of imports) {
462
+ if ((imp.module ?? "").length === 0) {
463
+ invalidImportPathCount++;
464
+ continue;
465
+ }
466
+ const importModule = String(imp.module ?? "");
467
+ const moduleSegments = importModule.split("/");
468
+ const stdlibRoot = moduleSegments.length >= 2 ? moduleSegments[0] ?? "" : "";
469
+ const isMultiSegmentStdlib = stdlibRoot.length > 0 && stdlibRoot !== "." && stdlibRoot !== ".." && GO_STDLIB_TOP_LEVEL.has(stdlibRoot);
470
+ relationships.push({
471
+ source: packageName || filePath,
472
+ target: imp.module || "",
473
+ // The FAMILY'S first-class `imports` kind, not a `references` edge
474
+ // (#799 round). An import names a MODULE, not a symbol: with the edge
475
+ // labelled `references`, every import path landed in the lane's
476
+ // references target surface, and an import path is a string literal
477
+ // (`net/http`, `github.com/google/go-cmp/cmp`) whose separators — `/`,
478
+ // `.`, `-` — no Go declaration may contain. R10 asks whether a target is
479
+ // a NAME; the answer for a module specifier is that it is a dependency,
480
+ // and the family's label for a dependency is `imports` (c moved to it in
481
+ // #910; go's own requiredKinds has named `imports` since the wave). The
482
+ // references surface is now separator-free, so a future foreign spelling
483
+ // there fails R10 immediately instead of hiding behind a recorded
484
+ // import-path convention.
485
+ type: ASTEngine.RelationshipType.Imports,
486
+ confidence: 0.9,
487
+ // isImport:true + isGoImportDep keep the edge pinnable as the "imports"
488
+ // relationship kind by completeness section B, which reads the MARKER
489
+ // (script/m14/completeness-inventory.ts, section B) — so the kind is
490
+ // claimed twice over and the evidence tier stays import_based.
491
+ // the import_spec, whose line is the path's line for a
492
+ // single import and for each entry of a grouped one (the spec starts
493
+ // at its path — an alias would share the line).
494
+ ...imp.range?.startLine ? { line_number: imp.range.startLine } : {},
495
+ metadata: {
496
+ isGoImportDep: true,
497
+ isImport: true,
498
+ packageName: imp.metadata?.packageName,
499
+ // THE ONE EXTERNAL CLAIM AN IMPORT EDGE CAN CARRY, and it is a claim about the NAME'S
500
+ // ORIGIN that the corpus cannot falsify because it is not about the corpus: the module is
501
+ // a member of the Go standard library's own package set (`GO_STDLIB_SINGLE_SEGMENT`
502
+ // above). `isStdlib` is one of the gate's recognised EXTERNAL_KEYS, so this converts a
503
+ // silent hole into a STATED, verifiable claim — never into a resolution, and the rule
504
+ // reads it only where the target names nothing, so no `resolved` row can move.
505
+ //
506
+ // ATTACHED ONLY TO SINGLE-SEGMENT PATHS, because the rule reaches a multi-segment one
507
+ // (`encoding/json`) as a PATH: `pathLikeTarget` sends it to `externalUnverified`, which
508
+ // is a clause-1 failure exactly as `unresolved` is. Claiming there would move an edge
509
+ // from one failure column into another and make the table look better without answering
510
+ // anything — so it is not done. MEASURED on the 400-file stride: this covers 830 of the
511
+ // lane's 25,215 unresolved edges and all 830 verify; the other 24,385 are untouched.
512
+ ...GO_STDLIB_SINGLE_SEGMENT.has(imp.module ?? "") ? { isStdlib: true } : {},
513
+ // Multi-segment stdlib citation (ruled mechanism above; imports edges only). The root
514
+ // listed is the MATCHED first segment, so the claim's vocabulary is exactly what the
515
+ // closed set proved; `stdlibModule` is evidence kept beside the claim for a reviewer
516
+ // (not a claim key). `isStdlib` is deliberately NOT added here: one claim family per
517
+ // edge, and `externalClaimOf` resolves the namespace claim first anyway.
518
+ ...isMultiSegmentStdlib ? {
519
+ languageNamespace: [stdlibRoot],
520
+ languageNamespacePolicy: "strict-absent",
521
+ languageNamespaceSlash: true,
522
+ stdlibModule: importModule
523
+ } : {}
524
+ }
525
+ });
526
+ }
527
+ const frameworks = this.detectGoFrameworks(imports);
528
+ const patterns = this.detectGoPatterns(rootNode, symbols);
529
+ const seenExportNames = /* @__PURE__ */ new Set();
530
+ for (let i = 0; i < symbols.length; i++) {
531
+ const s = symbols[i];
532
+ const isPrivate = s.visibility === "private";
533
+ const isVar = s.kind === ASTEngine.SymbolKind.Variable;
534
+ const isMethod = s.kind === ASTEngine.SymbolKind.Method;
535
+ if (!isPrivate && !isVar && !isMethod && !s.ownerName && !seenExportNames.has(s.name)) {
536
+ seenExportNames.add(s.name);
537
+ exports.push({ name: s.name, kind: s.kind, range: s.range, visibility: s.visibility || "public" });
538
+ }
539
+ }
540
+ if (packageName) {
541
+ const packageScopeNames = this.collectGoPackageScopeNames(rootNode);
542
+ for (const e of exports) {
543
+ if (!e.name || e.name === packageName) continue;
544
+ if (!packageScopeNames.has(e.name)) continue;
545
+ relationships.push({
546
+ source: packageName,
547
+ target: e.name,
548
+ type: ASTEngine.RelationshipType.Exports,
549
+ confidence: 1,
550
+ // the exported declaration's own line.
551
+ ...e.range?.startLine ? { line_number: e.range.startLine } : {},
552
+ metadata: {
553
+ relationshipType: "go_exported_identifier",
554
+ exportForm: "capitalized_package_scope_name",
555
+ symbolKind: String(e.kind ?? "")
556
+ }
557
+ });
558
+ }
559
+ }
560
+ this.extractDeclaredTypeRefs(rootNode, relationships, stdlibQualifiers, symbols);
561
+ let r7DroppedEdges = 0;
562
+ {
563
+ const kept = [];
564
+ for (const r of relationships) {
565
+ if (String(r.source ?? "").length > 0 && String(r.target ?? "").length > 0) kept.push(r);
566
+ else r7DroppedEdges++;
567
+ }
568
+ if (r7DroppedEdges > 0) relationships.splice(0, relationships.length, ...kept);
569
+ }
570
+ this.classifyEvidenceTier(relationships);
571
+ const cyclomaticComplexity = this.calculateCyclomaticComplexity(rootNode);
572
+ return {
573
+ language: "go",
574
+ symbols,
575
+ relationships,
576
+ imports,
577
+ exports,
578
+ errors,
579
+ metadata: {
580
+ // GO6 (go review): `complexity` was the synthetic
581
+ // symbols+relationships heuristic while a real per-file
582
+ // cyclomaticComplexity sat beside it — the exact disease the
583
+ // family removed everywhere else. One contract: both report the
584
+ // real cyclomatic number.
585
+ complexity: cyclomaticComplexity,
586
+ linesOfCode: ctx.content.split("\n").length,
587
+ // GO26 (round 5): computed ONCE. The pre-fix code called the counter
588
+ // twice on the same node, and each call is a full recursive walk of the
589
+ // tree — one wasted whole-file traversal per file, for two keys of the
590
+ // same object literal that must agree by construction.
591
+ cyclomaticComplexity,
592
+ framework: frameworks[0]?.name,
593
+ // Primary framework
594
+ patterns,
595
+ // Go intelligence metadata
596
+ detectedFrameworks: mapSymbols(frameworks, (f) => f.name),
597
+ frameworkPatterns: this.getGoFrameworkPatterns(
598
+ mapSymbols(frameworks, (f) => f.name),
599
+ symbols
600
+ ),
601
+ architecturalInsights: this.generateGoArchitecturalInsights(symbols, relationships),
602
+ architecturalScore: this.calculateArchitecturalScore(symbols, relationships, patterns),
603
+ codeQuality: this.assessCodeQuality(symbols, relationships, patterns),
604
+ hasTypeInference: true,
605
+ hasFrameworkIntelligence: true,
606
+ analysisVersion: "3.0.1-go-iterative-stateless",
607
+ // R7's two counters, published because a suppression a consumer cannot see is a
608
+ // suppression it cannot trust: `r7DroppedEdges` is every relationship the choke point
609
+ // refused for an empty source or target, `invalidImportPathCount` is the import subset of
610
+ // them. A valid file reports 0/0, which is what makes a non-zero value evidence.
611
+ r7DroppedEdges,
612
+ invalidImportPathCount
613
+ }
614
+ };
615
+ }
616
+ /**
617
+ * #419-D evidence-tier classification for EVERY emitted relationship
618
+ * (family rules — mirrors rust/c/cpp so the go analyzer is not the one
619
+ * sibling that leaves edges untagged, keeping completeness section D
620
+ * achievable). Metadata tags already set by the emitter are left intact.
621
+ */
622
+ classifyEvidenceTier(relationships) {
623
+ for (const r of relationships) {
624
+ const md = r.metadata ?? (r.metadata = {});
625
+ if (md.evidenceClass) continue;
626
+ const t = String(r.type ?? "");
627
+ const conf = typeof r.confidence === "number" ? r.confidence : 1;
628
+ if (t === "contains" || t === "exports") {
629
+ md.evidenceClass = "structural";
630
+ continue;
631
+ }
632
+ if (t === "imports") {
633
+ md.evidenceClass = "import_based";
634
+ continue;
635
+ }
636
+ if (t === "references") {
637
+ md.evidenceClass = md.relationshipType === "declared_type" || md.relationshipType === "return_type" ? "reference_qualified" : "usage_contextual";
638
+ continue;
639
+ }
640
+ if (t === "extends" || t === "implements" || t === "overrides" || t === "mixes_in") {
641
+ md.evidenceClass = conf >= 1 ? "structural" : conf >= 0.85 ? "usage_contextual" : "inferred";
642
+ continue;
643
+ }
644
+ if (t === "instantiates") {
645
+ md.evidenceClass = "structural";
646
+ continue;
647
+ }
648
+ md.evidenceClass = "usage_contextual";
649
+ }
650
+ for (const r of relationships) {
651
+ const conf = typeof r.confidence === "number" ? r.confidence : 1;
652
+ if (conf >= 1) continue;
653
+ const md = r.metadata ?? (r.metadata = {});
654
+ if (typeof md.reasonCode === "string" && md.reasonCode.length) continue;
655
+ const t = String(r.type ?? "");
656
+ const label = String(md.relationshipType ?? "");
657
+ const code = GO_SUB_UNIT_REASON[`${t}|${label}`] ?? GO_SUB_UNIT_REASON[`${t}|*`];
658
+ if (code) md.reasonCode = code;
659
+ }
660
+ for (const r of relationships) {
661
+ const t = String(r.type ?? "");
662
+ if (t === "contains" || t === "renders") continue;
663
+ const md = r.metadata ?? (r.metadata = {});
664
+ if (typeof md.bindReason === "string" && md.bindReason.length) continue;
665
+ const label = String(md.relationshipType ?? "");
666
+ const code = GO_SUB_UNIT_REASON[`${t}|${label}`] ?? GO_SUB_UNIT_REASON[`${t}|*`];
667
+ if (code) md.bindReason = code;
668
+ }
669
+ }
670
+ /**
671
+ * The names this file declares at PACKAGE SCOPE, read from the grammar.
672
+ *
673
+ * WHY POSITION AND NOT THE SYMBOL TABLE. The lane publishes a function-local
674
+ * `type` as a symbol on purpose (the declared-type pass attributes that type's
675
+ * field types to it, so dropping the symbol would publish a `references` edge
676
+ * whose SOURCE names nothing), and such a symbol carries no `ownerName`. So
677
+ * the symbol table cannot answer "is this name in package scope" — nothing in a
678
+ * `type_spec` symbol distinguishes line 10 from line 2946. The declaration's
679
+ * position can, and position is what Go's own scoping rule is written about.
680
+ *
681
+ * WHY THE DIRECT CHILDREN OF `source_file`. That is the grammar's own
682
+ * statement of package scope: `package_clause`, `import_declaration`, and the
683
+ * declarations are its direct named children, and everything else is reachable
684
+ * only by descending into a `function_declaration`, `method_declaration` or
685
+ * `func_literal` body — a `block`, which is not a direct child of `source_file`
686
+ * by construction.
687
+ *
688
+ * WHY ONE LEVEL OF SPEC DESCENT AND NO FURTHER. `type_declaration`,
689
+ * `var_declaration` and `const_declaration` nest their `type_spec`,
690
+ * `type_alias`, `var_spec`/`const_spec` children directly, and those two
691
+ * levels are all a package-scope declaration needs. A nested
692
+ * `function_declaration` is unreachable from here because it would be a child
693
+ * of a `block`.
694
+ *
695
+ * `type_alias` IS a sibling of `type_spec`, not a variant of it:
696
+ * `type_declaration` holds a choice of the two, so `type WidgetList =
697
+ * []*Widget` arrives as `type_alias` and `type Widget struct{}` as
698
+ * `type_spec`. Handling only `type_spec` dropped every exported ALIAS, which
699
+ * the pre-existing fixture `exports-contract` caught on the first run — see
700
+ * the commit for that row.
701
+ *
702
+ * `_` is the BLANK identifier. It has no name and names no graph node, so it is
703
+ * left out: the answer to "is this name at package scope" is not yes.
704
+ *
705
+ * `Set<string>`, so a declaration named `constructor` or `toString` cannot be
706
+ * confused with an inherited member — the same Object.prototype hazard the
707
+ * interface and promotion passes defend against separately.
708
+ */
709
+ collectGoPackageScopeNames(root) {
710
+ const names = /* @__PURE__ */ new Set();
711
+ const addSpecNames = (spec) => {
712
+ for (let i = 0; i < spec.namedChildCount; i++) {
713
+ const c = spec.namedChild(i);
714
+ if (!c) continue;
715
+ if (c.type !== "identifier" && c.type !== "type_identifier") continue;
716
+ const text = String(c.text ?? "");
717
+ if (text.length === 0 || text === "_") continue;
718
+ names.add(text);
719
+ }
720
+ };
721
+ for (let i = 0; i < root.namedChildCount; i++) {
722
+ const d = root.namedChild(i);
723
+ if (!d) continue;
724
+ if (d.type === "function_declaration" || d.type === "method_declaration") {
725
+ const nm = d.childForFieldName?.("name");
726
+ if (nm) {
727
+ const text = String(nm.text ?? "");
728
+ if (text.length > 0) names.add(text);
729
+ }
730
+ continue;
731
+ }
732
+ if (d.type !== "type_declaration" && d.type !== "var_declaration" && d.type !== "const_declaration") continue;
733
+ for (let j = 0; j < d.namedChildCount; j++) {
734
+ const spec = d.namedChild(j);
735
+ if (!spec) continue;
736
+ if (spec.type === "type_spec" || spec.type === "type_alias") {
737
+ const nm = spec.childForFieldName?.("name");
738
+ if (nm) {
739
+ const text = String(nm.text ?? "");
740
+ if (text.length > 0) names.add(text);
741
+ }
742
+ continue;
743
+ }
744
+ if (spec.type === "var_spec" || spec.type === "const_spec") {
745
+ addSpecNames(spec);
746
+ }
747
+ }
748
+ }
749
+ return names;
750
+ }
751
+ /**
752
+ * M14 — declared-type reference graph. Go type positions: struct fields,
753
+ * parameter declarations (params, receivers, and the SECOND parameter_list
754
+ * holding return values), var/const specs, and composite literal types.
755
+ * qualified_type keeps its dotted pkg.Type form per the M14 dotted-name
756
+ * convention; pointer/array/map/etc. wrappers descend; builtins excluded.
757
+ */
758
+ extractDeclaredTypeRefs(rootNode, relationships, stdlibQualifiers = /* @__PURE__ */ new Map(), symbols = []) {
759
+ const ownPackageName = this.goOwnPackageName(rootNode);
760
+ const emitted = /* @__PURE__ */ new Set();
761
+ const BUILTINS = /* @__PURE__ */ new Set(["int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "bool", "byte", "rune", "error", "any", "comparable"]);
762
+ const collectTypeNameNodes = (n, out, qualified) => {
763
+ if (n.type === "qualified_type" || n.type === "type_identifier") {
764
+ if (!BUILTINS.has(n.text) && !out.some((x) => x.text === n.text)) out.push(n);
765
+ return;
766
+ }
767
+ if (n.type === "block" || n.type === "expression_list") return;
768
+ for (let i = 0; i < n.namedChildCount; i++) {
769
+ const c = n.namedChild(i);
770
+ if (c) collectTypeNameNodes(c, out, qualified);
771
+ }
772
+ };
773
+ const resolveSource = (start) => {
774
+ let p = start.type === "field_declaration" ? start : start.parent;
775
+ let sawTypeSpec = false;
776
+ while (p) {
777
+ if (!sawTypeSpec && (p.type === "type_spec" || p.type === "type_alias")) {
778
+ for (let i = 0; i < p.namedChildCount; i++) {
779
+ const c = p.namedChild(i);
780
+ if (c && c.type === "type_identifier") return c.text;
781
+ }
782
+ sawTypeSpec = true;
783
+ }
784
+ if (p.type === "function_declaration" || p.type === "method_declaration") {
785
+ for (let i = 0; i < p.namedChildCount; i++) {
786
+ const c = p.namedChild(i);
787
+ if (!c) continue;
788
+ if (p.type === "method_declaration" && c.type === "field_identifier" || p.type === "function_declaration" && c.type === "identifier") return c.text;
789
+ }
790
+ }
791
+ p = p.parent;
792
+ }
793
+ return "global";
794
+ };
795
+ const emit = (source, roots, relType) => {
796
+ for (const r of roots) {
797
+ if (!r) continue;
798
+ const names = [];
799
+ collectTypeNameNodes(r, names);
800
+ for (const t of names) {
801
+ const qualified = t.type === "qualified_type" ? t.text : void 0;
802
+ const dot = qualified ? qualified.lastIndexOf(".") : -1;
803
+ const target = qualified && dot > 0 ? qualified.slice(dot + 1) : t.text;
804
+ const key = `${source}|${target}|${relType}`;
805
+ if (emitted.has(key)) continue;
806
+ emitted.add(key);
807
+ const stdlibModule = qualified !== void 0 ? this.goStdlibTypeClaim(qualified, target, symbols, stdlibQualifiers, ownPackageName) : null;
808
+ relationships.push({
809
+ source,
810
+ target,
811
+ type: ASTEngine.RelationshipType.References,
812
+ confidence: 0.95,
813
+ ...this.nodeLine(t) ? { line_number: this.nodeLine(t) } : {},
814
+ metadata: {
815
+ relationshipType: relType,
816
+ ...qualified ? { qualifiedType: qualified } : {},
817
+ ...stdlibModule !== null ? { isStdlib: true, stdlibPath: stdlibModule } : {}
818
+ }
819
+ });
820
+ }
821
+ }
822
+ };
823
+ const isGoResultParameter = (decl) => {
824
+ const list = decl.parent;
825
+ if (!list || list.type !== "parameter_list") return false;
826
+ const owner = list.parent;
827
+ if (!owner) return false;
828
+ if (owner.type !== "function_declaration" && owner.type !== "method_declaration" && owner.type !== "method_spec" && owner.type !== "func_literal") return false;
829
+ const result = owner.childForFieldName("result");
830
+ return !!result && result.id === list.id;
831
+ };
832
+ const walk = (n) => {
833
+ switch (n.type) {
834
+ case "field_declaration": {
835
+ emit(resolveSource(n), [n], "declared_type");
836
+ break;
837
+ }
838
+ case "function_declaration":
839
+ case "method_declaration":
840
+ case "func_literal":
841
+ case "method_spec": {
842
+ const result = n.childForFieldName("result");
843
+ if (result && result.type !== "parameter_list") emit(resolveSource(result), [result], "return_type");
844
+ break;
845
+ }
846
+ case "parameter_declaration":
847
+ case "variadic_parameter_declaration": {
848
+ emit(resolveSource(n), [n], isGoResultParameter(n) ? "return_type" : "declared_type");
849
+ break;
850
+ }
851
+ case "var_spec":
852
+ case "const_spec": {
853
+ const inFunction = (x) => {
854
+ for (let q = x; q; q = q.parent) {
855
+ if (q.type === "function_declaration" || q.type === "method_declaration" || q.type === "func_literal") return true;
856
+ }
857
+ return false;
858
+ };
859
+ if (!inFunction(n)) {
860
+ const declared = [];
861
+ for (let i = 0; i < n.namedChildCount; i++) {
862
+ const c = n.namedChild(i);
863
+ if (c && c.type === "identifier" && String(c.text ?? "") !== "_") declared.push(String(c.text ?? ""));
864
+ }
865
+ if (declared.length === 0) emit(resolveSource(n), [n], "declared_type");
866
+ else for (const name of declared) emit(name, [n], "declared_type");
867
+ } else {
868
+ emit(resolveSource(n), [n], "declared_type");
869
+ }
870
+ break;
871
+ }
872
+ }
873
+ for (let i = 0; i < n.namedChildCount; i++) {
874
+ const c = n.namedChild(i);
875
+ if (c) walk(c);
876
+ }
877
+ };
878
+ walk(rootNode);
879
+ }
880
+ /**
881
+ * PHASE 1: Extract Go symbols with proper Go semantics understanding
882
+ */
883
+ extractGoSymbols(node, symbols) {
884
+ const stack = [{ node, inFunction: false }];
885
+ while (stack.length > 0) {
886
+ const { node: current, inFunction } = stack.pop();
887
+ switch (current.type) {
888
+ case "function_declaration":
889
+ this.extractGoFunction(current, symbols);
890
+ break;
891
+ case "method_declaration":
892
+ this.extractGoMethod(current, symbols);
893
+ break;
894
+ case "type_declaration":
895
+ this.extractGoTypeDeclaration(current, symbols);
896
+ break;
897
+ case "var_declaration":
898
+ case "const_declaration":
899
+ if (!inFunction) this.extractGoVariables(current, symbols);
900
+ break;
901
+ case "import_declaration":
902
+ break;
903
+ }
904
+ const nextInFunction = inFunction || current.type === "block" || current.type === "function_declaration" || current.type === "method_declaration" || current.type === "func_literal";
905
+ for (let i = current.namedChildCount - 1; i >= 0; i--) {
906
+ const child = current.namedChild(i);
907
+ if (child) stack.push({ node: child, inFunction: nextInFunction });
908
+ }
909
+ }
910
+ }
911
+ /**
912
+ * Extract Go function declarations
913
+ */
914
+ extractGoFunction(node, symbols) {
915
+ const nameNode = node.childForFieldName("name");
916
+ if (!nameNode) return;
917
+ const functionName = nameNode.text;
918
+ const parameters = this.extractGoParameters(node);
919
+ const returnType = this.extractGoReturnType(node);
920
+ const qualifiedCalls = this.findGoFunctionCalls(node);
921
+ const bodyText = node.childForFieldName("body")?.text || "";
922
+ const apiContractPath = this.extractGoApiContractPathFromComments(node);
923
+ symbols.push({
924
+ name: functionName,
925
+ kind: ASTEngine.SymbolKind.Function,
926
+ range: this.nodeToRange(node),
927
+ parameters,
928
+ returnType,
929
+ qualified_calls: qualifiedCalls,
930
+ apiContractPath,
931
+ // Per-symbol cyclomatic complexity (family contract — rust/c/cpp all
932
+ // emit symbol.complexity; go was the only sibling that didn't, so the
933
+ // completeness section-C payload pin was impossible. Added go wave
934
+ // 2026-08-28, same counter as calculateCyclomaticComplexity).
935
+ complexity: this.calculateCyclomaticComplexity(node),
936
+ visibility: this.isGoExported(functionName) ? "public" : "private",
937
+ // A package-scope Go function has no owner, and that is a property of
938
+ // the language rather than of a missing argument: Go declares functions
939
+ // and methods at package scope only, and a nested function is a
940
+ // `func_literal`, which LocalsAreNotSymbols keeps out of the symbol set
941
+ // entirely. The two `!s.ownerName` constructor filters below are
942
+ // therefore correct on a fact about Go, not by accident of a dead
943
+ // parameter (GO26).
944
+ metadata: {
945
+ nodeType: node.type,
946
+ isGoFunction: true,
947
+ isExported: this.isGoExported(functionName),
948
+ hasErrorHandling: this.hasGoErrorHandling(bodyText),
949
+ isInit: functionName === "init",
950
+ hasGenerics: !!findSymbol(node.children, (c) => c && c.type === "type_parameter_list")
951
+ }
952
+ });
953
+ }
954
+ /**
955
+ * Extract Go method declarations (methods with receivers)
956
+ */
957
+ extractGoMethod(node, symbols) {
958
+ const nameNode = node.childForFieldName("name");
959
+ if (!nameNode) return;
960
+ const methodName = nameNode.text;
961
+ const receiver = this.extractGoReceiver(node);
962
+ const parameters = this.extractGoParameters(node);
963
+ const returnType = this.extractGoReturnType(node);
964
+ const qualifiedCalls = this.findGoFunctionCalls(node);
965
+ const bodyText = node.childForFieldName("body")?.text || "";
966
+ const apiContractPath = this.extractGoApiContractPathFromComments(node);
967
+ symbols.push({
968
+ name: methodName,
969
+ kind: ASTEngine.SymbolKind.Method,
970
+ range: this.nodeToRange(node),
971
+ parameters,
972
+ returnType,
973
+ qualified_calls: qualifiedCalls,
974
+ apiContractPath,
975
+ // Per-symbol cyclomatic complexity (family contract, go wave 2026-08-28).
976
+ complexity: this.calculateCyclomaticComplexity(node),
977
+ visibility: this.isGoExported(methodName) ? "public" : "private",
978
+ ownerName: receiver?.typeName,
979
+ ownerKind: ASTEngine.SymbolKind.Struct,
980
+ metadata: {
981
+ nodeType: node.type,
982
+ isGoMethod: true,
983
+ receiver,
984
+ receiverType: receiver?.typeName,
985
+ receiverKind: receiver?.isPointer ? "pointer" : "value",
986
+ isPointerReceiver: receiver?.isPointer || false,
987
+ isExported: this.isGoExported(methodName),
988
+ hasErrorHandling: this.hasGoErrorHandling(bodyText),
989
+ hasGenerics: !!findSymbol(node.children, (c) => c && c.type === "type_parameter_list")
990
+ }
991
+ });
992
+ }
993
+ /**
994
+ * Extract Go type declarations (structs, interfaces, type aliases)
995
+ */
996
+ extractGoTypeDeclaration(node, symbols) {
997
+ for (let i = 0; i < node.namedChildCount; i++) {
998
+ const typeSpec = node.namedChild(i);
999
+ if (typeSpec?.type === "type_spec" || typeSpec?.type === "type_alias") {
1000
+ this.extractGoTypeSpec(typeSpec, symbols);
1001
+ }
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Extract individual type specifications (struct, interface, etc.)
1006
+ */
1007
+ extractGoTypeSpec(node, symbols) {
1008
+ const nameNode = node.childForFieldName("name");
1009
+ if (!nameNode) return;
1010
+ const typeName = nameNode.text;
1011
+ const typeNode = node.childForFieldName("type");
1012
+ if (!typeNode) return;
1013
+ const typeParamsNode = findSymbol(node.children, (c) => c && c.type === "type_parameter_list");
1014
+ const hasGenerics = !!typeParamsNode;
1015
+ let kind;
1016
+ let isInterface = false;
1017
+ switch (typeNode.type) {
1018
+ case "struct_type":
1019
+ kind = ASTEngine.SymbolKind.Struct;
1020
+ break;
1021
+ case "interface_type":
1022
+ kind = ASTEngine.SymbolKind.Interface;
1023
+ isInterface = true;
1024
+ break;
1025
+ default:
1026
+ kind = ASTEngine.SymbolKind.Type;
1027
+ }
1028
+ symbols.push({
1029
+ name: typeName,
1030
+ kind,
1031
+ range: this.nodeToRange(node),
1032
+ visibility: this.isGoExported(typeName) ? "public" : "private",
1033
+ metadata: {
1034
+ nodeType: node.type,
1035
+ goType: typeNode.type,
1036
+ isInterface,
1037
+ isStruct: typeNode.type === "struct_type",
1038
+ isGoStruct: typeNode.type === "struct_type",
1039
+ isExported: this.isGoExported(typeName),
1040
+ isTypeAlias: node.type === "type_alias",
1041
+ hasGenerics
1042
+ }
1043
+ });
1044
+ if (typeNode.type === "struct_type") {
1045
+ this.extractStructFields(typeNode, symbols, typeName);
1046
+ } else if (typeNode.type === "interface_type") {
1047
+ this.extractInterfaceMethods(typeNode, symbols, typeName);
1048
+ }
1049
+ }
1050
+ /**
1051
+ * Extract struct fields
1052
+ */
1053
+ extractStructFields(structNode, symbols, structName) {
1054
+ for (let i = 0; i < structNode.namedChildCount; i++) {
1055
+ const child = structNode.namedChild(i);
1056
+ if (child?.type === "field_declaration_list") {
1057
+ for (let j = 0; j < child.namedChildCount; j++) {
1058
+ const fieldDecl = child.namedChild(j);
1059
+ if (fieldDecl?.type === "field_declaration") {
1060
+ const fieldTypeNode = fieldDecl.childForFieldName("type");
1061
+ const declaredNameNodes = [];
1062
+ const seenFieldStart = /* @__PURE__ */ new Set();
1063
+ const addDeclaredName = (n) => {
1064
+ if (!n) return;
1065
+ const at = n.startIndex ?? -1;
1066
+ if (seenFieldStart.has(at)) return;
1067
+ seenFieldStart.add(at);
1068
+ declaredNameNodes.push(n);
1069
+ };
1070
+ addDeclaredName(fieldDecl.childForFieldName("name"));
1071
+ for (let k = 0; k < fieldDecl.namedChildCount; k++) {
1072
+ const c = fieldDecl.namedChild(k);
1073
+ if (c && c.type === "field_identifier") addDeclaredName(c);
1074
+ }
1075
+ const fieldNameNodes = declaredNameNodes.filter((n) => n.text !== "_");
1076
+ const hasDeclaredName = declaredNameNodes.length > 0;
1077
+ const tagNode = fieldDecl.childForFieldName("tag");
1078
+ let structTags = /* @__PURE__ */ Object.create(null);
1079
+ if (tagNode) {
1080
+ const tagText = this.stripRawStringDelimiters(tagNode.text);
1081
+ structTags = this.parseStructTags(tagText);
1082
+ }
1083
+ if (!hasDeclaredName && fieldTypeNode) {
1084
+ const embeddedName = this.goEmbeddedFieldName(fieldTypeNode);
1085
+ if (embeddedName) {
1086
+ symbols.push({
1087
+ name: embeddedName,
1088
+ kind: ASTEngine.SymbolKind.Property,
1089
+ range: this.nodeToRange(fieldTypeNode),
1090
+ returnType: fieldTypeNode.text,
1091
+ visibility: this.isGoExported(embeddedName) ? "public" : "private",
1092
+ ownerName: structName,
1093
+ ownerKind: ASTEngine.SymbolKind.Struct,
1094
+ metadata: {
1095
+ structTags: getObjectKeysLength(structTags) > 0 ? structTags : void 0,
1096
+ nodeType: fieldDecl.type,
1097
+ isStructField: true,
1098
+ fieldType: fieldTypeNode.text,
1099
+ isExported: this.isGoExported(embeddedName),
1100
+ isEmbeddedField: true,
1101
+ embeddedField: fieldTypeNode.text
1102
+ }
1103
+ });
1104
+ }
1105
+ }
1106
+ for (const fieldNameNode of fieldNameNodes) {
1107
+ symbols.push({
1108
+ name: fieldNameNode.text,
1109
+ kind: ASTEngine.SymbolKind.Property,
1110
+ // The FIELD's own span, not the whole declaration: `A, B int`
1111
+ // names two fields at two positions, and a consumer that jumps
1112
+ // to `B` must land on `B` (#919's range rule).
1113
+ range: this.nodeToRange(fieldNameNode),
1114
+ returnType: fieldTypeNode?.text,
1115
+ visibility: this.isGoExported(fieldNameNode.text) ? "public" : "private",
1116
+ ownerName: structName,
1117
+ ownerKind: ASTEngine.SymbolKind.Struct,
1118
+ metadata: {
1119
+ structTags: getObjectKeysLength(structTags) > 0 ? structTags : void 0,
1120
+ nodeType: fieldDecl.type,
1121
+ isStructField: true,
1122
+ fieldType: fieldTypeNode?.text,
1123
+ isExported: this.isGoExported(fieldNameNode.text),
1124
+ // #889: this field shares its declaration with others
1125
+ groupedFieldDeclaration: fieldNameNodes.length > 1
1126
+ }
1127
+ });
1128
+ }
1129
+ }
1130
+ }
1131
+ break;
1132
+ }
1133
+ }
1134
+ }
1135
+ /**
1136
+ * The NAME an embedded field declares.
1137
+ *
1138
+ * Go's spec: "A field declared with a type but no explicit field name is
1139
+ * called an embedded field ... The unqualified type name acts as the field
1140
+ * name." So `io.Reader` declares a field `Reader`, `*S2` declares `S2`, and
1141
+ * `T[P]` declares `T` — the same LEAF rule the embedded-INTERFACE path in
1142
+ * `extractInterfaceMethods` already applies. Wrappers are transparent; only
1143
+ * the leaf names the field.
1144
+ */
1145
+ goEmbeddedFieldName(typeNode) {
1146
+ let cur = typeNode;
1147
+ for (let guard = 0; guard < 8 && cur; guard++) {
1148
+ const t = cur.type;
1149
+ if (t === "pointer_type") {
1150
+ cur = (cur.namedChildCount > 0 ? cur.namedChild(0) : void 0) ?? void 0;
1151
+ continue;
1152
+ }
1153
+ if (t === "generic_type") {
1154
+ cur = (cur.namedChildCount > 0 ? cur.namedChild(0) : void 0) ?? void 0;
1155
+ continue;
1156
+ }
1157
+ break;
1158
+ }
1159
+ if (!cur) return void 0;
1160
+ if (cur.type !== "type_identifier" && cur.type !== "qualified_type") return void 0;
1161
+ const raw = cur.text;
1162
+ const dot = raw.lastIndexOf(".");
1163
+ const leaf = dot !== -1 ? raw.slice(dot + 1) : raw;
1164
+ return this.isPlainGoIdentifier(leaf) ? leaf : void 0;
1165
+ }
1166
+ /**
1167
+ * Extract interface methods
1168
+ */
1169
+ extractInterfaceMethods(interfaceNode, symbols, interfaceName) {
1170
+ for (let i = 0; i < interfaceNode.namedChildCount; i++) {
1171
+ const method = interfaceNode.namedChild(i);
1172
+ if (method?.type === "method_spec") {
1173
+ const methodName = method.childForFieldName("name");
1174
+ if (methodName) {
1175
+ const parameters = this.extractGoMethodSpecParameters(method);
1176
+ const returnType = this.extractGoMethodSpecReturnType(method);
1177
+ symbols.push({
1178
+ name: methodName.text,
1179
+ kind: ASTEngine.SymbolKind.Method,
1180
+ range: this.nodeToRange(method),
1181
+ isAbstract: true,
1182
+ // Interface methods are abstract
1183
+ parameters,
1184
+ returnType,
1185
+ visibility: this.isGoExported(methodName.text) ? "public" : "private",
1186
+ ownerName: interfaceName,
1187
+ ownerKind: ASTEngine.SymbolKind.Interface,
1188
+ metadata: {
1189
+ nodeType: method.type,
1190
+ isInterfaceMethod: true,
1191
+ isExported: this.isGoExported(methodName.text)
1192
+ }
1193
+ });
1194
+ }
1195
+ continue;
1196
+ }
1197
+ if (method?.type === "constraint_elem") {
1198
+ const embeddedNode = method.namedChildCount > 0 ? method.namedChild(0) : void 0;
1199
+ if (embeddedNode && (embeddedNode.type === "type_identifier" || embeddedNode.type === "qualified_type")) {
1200
+ const rawText = embeddedNode.text;
1201
+ const leaf = rawText.lastIndexOf(".") !== -1 ? rawText.slice(rawText.lastIndexOf(".") + 1) : rawText;
1202
+ if (leaf && this.isPlainGoIdentifier(leaf)) {
1203
+ symbols.push({
1204
+ name: leaf,
1205
+ kind: ASTEngine.SymbolKind.Interface,
1206
+ range: this.nodeToRange(method),
1207
+ visibility: "public",
1208
+ ownerName: interfaceName,
1209
+ ownerKind: ASTEngine.SymbolKind.Interface,
1210
+ metadata: {
1211
+ nodeType: method.type,
1212
+ isEmbeddedInterface: true,
1213
+ embeddedInterface: rawText
1214
+ }
1215
+ });
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ /**
1222
+ * Extract Go variables and constants
1223
+ */
1224
+ extractGoVariables(node, symbols) {
1225
+ for (let i = 0; i < node.namedChildCount; i++) {
1226
+ const spec = node.namedChild(i);
1227
+ if (spec?.type === "var_spec" || spec?.type === "const_spec") {
1228
+ const nameNodes = [];
1229
+ for (let j = 0; j < spec.namedChildCount; j++) {
1230
+ const c = spec.namedChild(j);
1231
+ if (c && c.type === "identifier" && c.text !== "_") nameNodes.push(c);
1232
+ }
1233
+ if (nameNodes.length > 0) {
1234
+ const isConst = spec.type === "const_spec";
1235
+ for (const nameNode of nameNodes) {
1236
+ if (!nameNode) continue;
1237
+ symbols.push({
1238
+ name: nameNode.text,
1239
+ kind: isConst ? ASTEngine.SymbolKind.Constant : ASTEngine.SymbolKind.Variable,
1240
+ // The NAME's own span, not the whole `var_spec` — the same
1241
+ // #919 range rule the struct-field path applies (and the reason
1242
+ // it exists: `A, B int` names two fields at two positions, and a
1243
+ // consumer that jumps to `B` must land on `B`). The var/const
1244
+ // path kept the declaration's range, so on
1245
+ // `var alpha,\n\tbeta int` BOTH names reported `3:5` — `beta`'s
1246
+ // own position never appeared anywhere in the index.
1247
+ range: this.nodeToRange(nameNode),
1248
+ visibility: this.isGoExported(nameNode.text) ? "public" : "private",
1249
+ metadata: {
1250
+ nodeType: spec.type,
1251
+ isConst,
1252
+ isExported: this.isGoExported(nameNode.text)
1253
+ }
1254
+ });
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ // HELPER METHODS
1261
+ /** GO3: file has a dot-import (`import . "fmt"`) — bare identifiers may
1262
+ * name dot-imported package members, which no local symbol can resolve.
1263
+ * Calls resolve leniently (documented); bare READS stay strict. */
1264
+ hasGoDotImport(rootNode) {
1265
+ const stack = [rootNode];
1266
+ while (stack.length > 0) {
1267
+ const n = stack.pop();
1268
+ if (n.type === "import_spec") {
1269
+ for (let i = 0; i < n.namedChildCount; i++) {
1270
+ const c = n.namedChild(i);
1271
+ if (c && c.type === "dot") return true;
1272
+ }
1273
+ }
1274
+ for (let i = n.namedChildCount - 1; i >= 0; i--) {
1275
+ const c = n.namedChild(i);
1276
+ if (c) stack.push(c);
1277
+ }
1278
+ }
1279
+ return false;
1280
+ }
1281
+ /** GO1: identifiers under a qualified_type (`pkg.Foo`) name types, never
1282
+ * values — the composite-literal and declared-type passes own those. */
1283
+ hasGoQualifiedAncestor(node) {
1284
+ let cur = node.parent;
1285
+ while (cur) {
1286
+ if (cur.type === "qualified_type") return true;
1287
+ if (cur.type === "call_expression" || cur.type === "field_declaration" || cur.type === "function_declaration" || cur.type === "method_declaration" || cur.type === "block" || cur.type === "composite_literal" || cur.type === "var_spec" || cur.type === "const_spec" || cur.type === "short_var_declaration" || cur.type === "assignment_statement") return false;
1288
+ cur = cur.parent;
1289
+ }
1290
+ return false;
1291
+ }
1292
+ nodeToRange(node) {
1293
+ return {
1294
+ startLine: node.startPosition.row + 1,
1295
+ startColumn: node.startPosition.column + 1,
1296
+ endLine: node.endPosition.row + 1,
1297
+ endColumn: node.endPosition.column + 1,
1298
+ startIndex: node.startIndex,
1299
+ endIndex: node.endIndex
1300
+ };
1301
+ }
1302
+ /**
1303
+ * Is a Go identifier EXPORTED?
1304
+ *
1305
+ * Go's spec: "An identifier is exported if the first character of the
1306
+ * identifier's name is a Unicode upper case letter" — the rule is
1307
+ * `unicode.IsUpper`, NOT an ASCII range. The old test here was
1308
+ * `name[0] >= "A" && name[0] <= "Z"`, so every legal Go identifier with a
1309
+ * non-ASCII head (`Ünï`, `Ö`, `Ñ`) was published `visibility: "private"`,
1310
+ * which fails the PHASE-5 export predicate below and removed it from the
1311
+ * public-API list the product serves. Go identifiers may use any Unicode
1312
+ * letter (spec, Identifiers), so this was a real hole in the export
1313
+ * surface, not a cosmetic classification.
1314
+ *
1315
+ * The implementation is a no-regex upper-case test on the FIRST CODE POINT
1316
+ * using the JS runtime's own Unicode case mappings (the same ICU tables the
1317
+ * grammar's identifier rule is written against):
1318
+ *
1319
+ * c is an UPPER CASE letter <=> toUpperCase(c) === c AND toLowerCase(c) !== c
1320
+ *
1321
+ * which is exactly `unicode.IsUpper`'s definition — Go's own spec words the
1322
+ * category test as "the uppercase version of the letter is different from
1323
+ * its lowercase version". Spot-checked against the categories that matter:
1324
+ * `A` upper, `a` not, `Ü` upper, `ü` not, `ß` not (Go: Ll, not Lu), CJK
1325
+ * `U+4E2D` not (Lo, not Lu), titlecase `Dž` not (Lt, not Lu). The only residual is
1326
+ * a code point the runtime's tables do not know, which is then not-upper —
1327
+ * the conservative direction (a symbol stays private rather than being
1328
+ * published as public API on a guess).
1329
+ */
1330
+ isGoExported(name) {
1331
+ if (!name) return false;
1332
+ const c = String.fromCodePoint(name.codePointAt(0));
1333
+ if (c.length === 0) return false;
1334
+ if (c === "\uFFFD" && name.charCodeAt(0) >= 55296 && name.charCodeAt(0) <= 57343) return false;
1335
+ const upper = c.toUpperCase();
1336
+ if (upper !== c) return false;
1337
+ return c.toLowerCase() !== c;
1338
+ }
1339
+ extractGoApiContractPathFromComments(node) {
1340
+ let prevNode = node.previousNamedSibling;
1341
+ const comments = [];
1342
+ while (prevNode && prevNode.type === "comment") {
1343
+ comments.unshift(prevNode.text);
1344
+ prevNode = prevNode.previousNamedSibling;
1345
+ }
1346
+ for (const comment of comments) {
1347
+ const router = this.parseRouterAnnotation(comment);
1348
+ if (router) return router;
1349
+ }
1350
+ return void 0;
1351
+ }
1352
+ extractGoReceiver(methodNode) {
1353
+ let receiverList = null;
1354
+ for (let i = 0; i < methodNode.namedChildCount; i++) {
1355
+ const child = methodNode.namedChild(i);
1356
+ if (child?.type === "parameter_list") {
1357
+ receiverList = child;
1358
+ break;
1359
+ }
1360
+ }
1361
+ if (!receiverList) return void 0;
1362
+ const receiverParam = receiverList.namedChild(0);
1363
+ if (!receiverParam || receiverParam.type !== "parameter_declaration") return void 0;
1364
+ const receiverName = receiverParam.childForFieldName("name");
1365
+ const receiverType = receiverParam.childForFieldName("type");
1366
+ if (!receiverType) return void 0;
1367
+ const typeName = receiverType.text;
1368
+ const isPointer = typeName.startsWith("*");
1369
+ let actualTypeName = isPointer ? typeName.substring(1) : typeName;
1370
+ const lt = actualTypeName.indexOf("[");
1371
+ if (lt > 0) actualTypeName = actualTypeName.slice(0, lt);
1372
+ return {
1373
+ name: receiverName?.text || "",
1374
+ typeName: actualTypeName,
1375
+ isPointer
1376
+ };
1377
+ }
1378
+ extractGoParameters(node) {
1379
+ const parameters = [];
1380
+ let paramLists = [];
1381
+ for (let i = 0; i < node.namedChildCount; i++) {
1382
+ const child = node.namedChild(i);
1383
+ if (child?.type === "parameter_list") {
1384
+ paramLists.push(child);
1385
+ }
1386
+ }
1387
+ const targetParamList = node.type === "method_declaration" ? paramLists[1] : paramLists[0];
1388
+ if (!targetParamList) return parameters;
1389
+ for (let i = 0; i < targetParamList.namedChildCount; i++) {
1390
+ const param = targetParamList.namedChild(i);
1391
+ if (param?.type === "parameter_declaration" || param?.type === "variadic_parameter_declaration") {
1392
+ const isVariadic = param.type === "variadic_parameter_declaration";
1393
+ const nameNodes = [];
1394
+ for (let j = 0; j < param.namedChildCount; j++) {
1395
+ const c = param.namedChild(j);
1396
+ if (c && c.type === "identifier") nameNodes.push(c);
1397
+ }
1398
+ const typeNode = param.childForFieldName("type");
1399
+ if (nameNodes.length > 0) {
1400
+ for (const nameNode of nameNodes) {
1401
+ if (!nameNode) continue;
1402
+ parameters.push({
1403
+ name: nameNode.text,
1404
+ type: typeNode?.text,
1405
+ isOptional: false,
1406
+ // Go doesn't have optional parameters
1407
+ ...isVariadic ? { isVariadic: true } : {}
1408
+ });
1409
+ }
1410
+ } else if (typeNode) {
1411
+ parameters.push({
1412
+ name: `arg${parameters.length}`,
1413
+ type: typeNode?.text,
1414
+ isOptional: false,
1415
+ // Go doesn't have optional parameters
1416
+ ...isVariadic ? { isVariadic: true } : {}
1417
+ });
1418
+ }
1419
+ }
1420
+ }
1421
+ return parameters;
1422
+ }
1423
+ extractGoReturnType(node) {
1424
+ const resultNode = node.childForFieldName("result");
1425
+ if (resultNode) {
1426
+ if (resultNode.type === "parameter_list") {
1427
+ const types = [];
1428
+ for (let i = 0; i < resultNode.namedChildCount; i++) {
1429
+ const p = resultNode.namedChild(i);
1430
+ if (!p || p.type !== "parameter_declaration") continue;
1431
+ const t = p.childForFieldName("type");
1432
+ if (!t) continue;
1433
+ let names = 0;
1434
+ for (let j = 0; j < p.namedChildCount; j++) {
1435
+ const c = p.namedChild(j);
1436
+ if (c && c.type === "identifier") names++;
1437
+ }
1438
+ for (let k = 0; k < Math.max(1, names); k++) types.push(t.text);
1439
+ }
1440
+ if (types.length === 1) return types[0];
1441
+ if (types.length > 1) return `(${types.join(", ")})`;
1442
+ return resultNode.text;
1443
+ }
1444
+ return resultNode.text;
1445
+ }
1446
+ for (let i = 0; i < node.namedChildCount; i++) {
1447
+ const child = node.namedChild(i);
1448
+ if (child && inArray(
1449
+ [
1450
+ "type_identifier",
1451
+ "qualified_type",
1452
+ "pointer_type",
1453
+ "slice_type",
1454
+ "array_type",
1455
+ "map_type",
1456
+ "channel_type",
1457
+ "function_type"
1458
+ ],
1459
+ child.type
1460
+ )) {
1461
+ return child.text;
1462
+ }
1463
+ }
1464
+ return void 0;
1465
+ }
1466
+ /**
1467
+ * the 1-based line of a syntax node, or `undefined` when the
1468
+ * node is absent. Never 0 — a site that cannot be named is OMITTED from the
1469
+ * edge (the #820 rule), so a reader can tell "no line" from "line 1".
1470
+ */
1471
+ nodeLine(node) {
1472
+ if (!node || !node.startPosition || typeof node.startPosition.row !== "number") return void 0;
1473
+ return node.startPosition.row + 1;
1474
+ }
1475
+ /**
1476
+ * the node that NAMES a call. A `selector_expression` starts at
1477
+ * its RECEIVER, and a receiver-continued chain is valid Go — `.` does not
1478
+ * insert a semicolon, so `builder.` + newline + `Reset()` parses — which makes
1479
+ * the selector's own start the wrong site exactly as the c lane measured on a
1480
+ * newline-continued function-pointer call. The `field` child is the leaf the
1481
+ * edge's target is built from, so it is the leaf that carries the line.
1482
+ */
1483
+ goCalleeNameNode(funcNode) {
1484
+ if (!funcNode) return void 0;
1485
+ if (funcNode.type === "selector_expression") {
1486
+ return funcNode.childForFieldName("field") ?? funcNode;
1487
+ }
1488
+ return funcNode;
1489
+ }
1490
+ /**
1491
+ * the callee leaf of a STATEMENT-level call — `defer`/`go`
1492
+ * statements. The statement starts at its keyword (and `defer` does not insert
1493
+ * a semicolon, so `defer` + newline + `cleanup()` is valid Go), so the keyword
1494
+ * is not where the target is named.
1495
+ */
1496
+ goStatementCalleeNode(node) {
1497
+ const callExpr = this.findChildByType(node, "call_expression");
1498
+ return callExpr ? this.goCalleeNameNode(callExpr.childForFieldName("function")) : void 0;
1499
+ }
1500
+ findGoFunctionCalls(functionNode) {
1501
+ const calls = [];
1502
+ const sites = /* @__PURE__ */ new Map();
1503
+ const seen = /* @__PURE__ */ new Set();
1504
+ const bodyNode = functionNode.childForFieldName("body");
1505
+ if (bodyNode) {
1506
+ const stack = [bodyNode];
1507
+ while (stack.length > 0) {
1508
+ const node = stack.pop();
1509
+ if (node.type === "call_expression") {
1510
+ const funcNode = node.childForFieldName("function");
1511
+ if (funcNode) {
1512
+ const callExpression = this.buildGoCallExpression(funcNode);
1513
+ if (callExpression && !this.isGoBuiltin(callExpression) && !seen.has(callExpression)) {
1514
+ seen.add(callExpression);
1515
+ calls.push(callExpression);
1516
+ sites.set(callExpression, this.goCalleeNameNode(funcNode) ?? funcNode);
1517
+ }
1518
+ }
1519
+ }
1520
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
1521
+ const child = node.namedChild(i);
1522
+ if (child) stack.push(child);
1523
+ }
1524
+ }
1525
+ }
1526
+ this.goCallSites.set(functionNode.startIndex, sites);
1527
+ return calls;
1528
+ }
1529
+ buildGoCallExpression(funcNode) {
1530
+ switch (funcNode.type) {
1531
+ case "identifier":
1532
+ return funcNode.text;
1533
+ case "selector_expression":
1534
+ return this.buildGoSelectorExpression(funcNode);
1535
+ default:
1536
+ return void 0;
1537
+ }
1538
+ }
1539
+ buildGoSelectorExpression(node) {
1540
+ const object = node.childForFieldName("operand");
1541
+ const field = node.childForFieldName("field");
1542
+ if (!object || !field) return node.text;
1543
+ const objectText = object.type === "selector_expression" ? this.buildGoSelectorExpression(object) : object.text;
1544
+ return `${objectText}.${field.text}`;
1545
+ }
1546
+ isGoBuiltin(call) {
1547
+ const builtins = [
1548
+ "make",
1549
+ "new",
1550
+ "len",
1551
+ "cap",
1552
+ "append",
1553
+ "copy",
1554
+ "delete",
1555
+ "panic",
1556
+ "recover",
1557
+ "print",
1558
+ "println",
1559
+ "close"
1560
+ ];
1561
+ const segs = call.split(".");
1562
+ return inArray(builtins, segs[segs.length - 1]);
1563
+ }
1564
+ /**
1565
+ * Resolve a goroutine/defer call target through the SAME bare-call symbol
1566
+ * contract as analyzeGoFunctionCalls: package-qualified (has a dot) emits
1567
+ * the leaf unconditionally; bare names emit only if they name an in-file
1568
+ * declared function/method symbol. This drops closure-variable callees
1569
+ * (`go done()`, `defer release()` where done/release are var-assigned
1570
+ * funcs, NOT declared symbols) so goroutine/defer edges stay consistent
1571
+ * with the analyzer's own call resolution and with the differential
1572
+ * oracle — which derives only declared-function + qualified leaves.
1573
+ * GO3: with a dot-import in the file, bare names may be dot-imported
1574
+ * members — resolve leniently (besides builtins, filtered by callers).
1575
+ */
1576
+ /**
1577
+ * Is this symbol a name a BARE call expression in this file could resolve to?
1578
+ *
1579
+ * A Go `method_spec` is published as `kind: Method` (it is a method
1580
+ * declaration, in an interface body), and both the bare-call resolver and the
1581
+ * package-scope-call predicate accepted it. So a name that exists in the file
1582
+ * ONLY as an interface method — `type I interface{ Handle(x int) error }`
1583
+ * with no `func Handle` anywhere — resolved as a package-scope callable and
1584
+ * the analyzer emitted `calls global -> Handle`.
1585
+ *
1586
+ * That edge is a FABRICATION, and the differential called it one
1587
+ * (`UNEXPLAINED`): the F5 oracle's `declaredFns` is built from
1588
+ * `function_declaration` + `method_declaration` only, because those are the
1589
+ * only two forms a bare callee can name in Go. A `method_spec` is not in
1590
+ * scope as a package identifier at all — `Handle(1)` in that file does not
1591
+ * compile. The two sides disagreed, which is the C5 asymmetry class (the
1592
+ * mirror-image defect the lane hit in round 4), and the corpora could not
1593
+ * see it: a file that declares an interface method and calls that name bare
1594
+ * is not a shape either pinned corpus happens to contain.
1595
+ *
1596
+ * The rule is one predicate, applied at all three call-resolution sites, so
1597
+ * the resolver and the two go/defer handlers cannot drift apart again.
1598
+ */
1599
+ isGoPackageCallable(s) {
1600
+ if (s.kind !== ASTEngine.SymbolKind.Function && s.kind !== ASTEngine.SymbolKind.Method) return false;
1601
+ if (s.metadata?.isInterfaceMethod) return false;
1602
+ return true;
1603
+ }
1604
+ isGoResolvableCallTarget(target, symbols, dotImported = false) {
1605
+ if (!target) return false;
1606
+ if (target.indexOf(".") !== -1) return true;
1607
+ if (dotImported) return true;
1608
+ return someSymbol(symbols, (s) => this.isGoPackageCallable(s) && s.name === target);
1609
+ }
1610
+ /**
1611
+ * Resolve the callable LEAF name of a callee node, for goroutine/defer/
1612
+ * channel handlers. Anonymous func-literal callees (`go func(){...}()`,
1613
+ * `defer func(){...}()`) and non-named callees have NO static name and
1614
+ * must be skipped — the pre-fix handlers returned the whole func-literal
1615
+ * text / "anonymous_closure", fabricating garbage calls targets (found
1616
+ * #261-family Go wave 2026-08-28, same class as Rust's `(self.0)` fix).
1617
+ * Selector callees (`s.start`) resolve to the field LEAF (family contract).
1618
+ */
1619
+ resolveGoCallLeaf(funcNode) {
1620
+ if (!funcNode) return void 0;
1621
+ if (funcNode.type === "identifier") return funcNode.text;
1622
+ if (funcNode.type === "selector_expression") {
1623
+ const field = funcNode.childForFieldName("field");
1624
+ return field?.text;
1625
+ }
1626
+ return void 0;
1627
+ }
1628
+ // Framework detection
1629
+ detectGoFrameworks(imports) {
1630
+ const frameworks = [];
1631
+ const modules = mapSymbols(imports, (imp) => imp.module || "");
1632
+ forEachSymbol(GO_FRAMEWORK_RULES, (rule) => {
1633
+ if (!rule.listed) return;
1634
+ if (!someSymbol(modules, (m) => rule.matches(m))) return;
1635
+ frameworks.push({ name: rule.name, confidence: rule.confidence, patterns: [rule.name] });
1636
+ });
1637
+ return frameworks;
1638
+ }
1639
+ detectGoPatterns(rootNode, symbols) {
1640
+ const patterns = [];
1641
+ const stack = [rootNode];
1642
+ while (stack.length > 0) {
1643
+ const node = stack.pop();
1644
+ switch (node.type) {
1645
+ case "go_statement":
1646
+ if (!inArray(patterns, "goroutines")) patterns.push("goroutines");
1647
+ break;
1648
+ case "defer_statement":
1649
+ if (!inArray(patterns, "defer")) patterns.push("defer");
1650
+ break;
1651
+ case "send_statement":
1652
+ case "receive_statement":
1653
+ case "communication_case":
1654
+ case "channel_type":
1655
+ if (!inArray(patterns, "channels")) patterns.push("channels");
1656
+ break;
1657
+ case "interface_type":
1658
+ if (node.namedChildCount === 0) {
1659
+ if (!inArray(patterns, "empty_interface")) patterns.push("empty_interface");
1660
+ }
1661
+ break;
1662
+ case "if_statement":
1663
+ const condition = node.childForFieldName("condition");
1664
+ if (condition && stringIncludes(condition.text, "err") && stringIncludes(condition.text, "!=")) {
1665
+ if (!inArray(patterns, "error_handling")) patterns.push("error_handling");
1666
+ }
1667
+ break;
1668
+ }
1669
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
1670
+ const child = node.namedChild(i);
1671
+ if (child) stack.push(child);
1672
+ }
1673
+ }
1674
+ if (someSymbol(symbols, (s) => s.metadata?.isInterface)) {
1675
+ if (!inArray(patterns, "interfaces")) patterns.push("interfaces");
1676
+ }
1677
+ if (someSymbol(symbols, (s) => s.metadata?.isStruct)) {
1678
+ if (!inArray(patterns, "structs")) patterns.push("structs");
1679
+ }
1680
+ return patterns;
1681
+ }
1682
+ /**
1683
+ * Go Relationship Analysis
1684
+ * Analyzes Go-specific relationships: struct embedding, interface implementation, method receivers, package dependencies
1685
+ */
1686
+ analyzeGoRelationships(rootNode, relationships, symbols, _ctx, stdlibQualifiers = /* @__PURE__ */ new Map()) {
1687
+ this.analyzeGoStructEmbedding(rootNode, relationships);
1688
+ this.analyzeGoMethodPromotionShadowing(relationships, symbols);
1689
+ this.analyzeGoInterfaceImplementation(rootNode, relationships, symbols);
1690
+ this.analyzeGoFunctionCalls(rootNode, relationships, symbols, stdlibQualifiers);
1691
+ this.analyzeGoPackageScopeCalls(rootNode, relationships, symbols, stdlibQualifiers);
1692
+ this.analyzeGoCompositeLiteralConstruction(rootNode, relationships, symbols, stdlibQualifiers);
1693
+ this.analyzeGoSpecificPatterns(rootNode, relationships, symbols);
1694
+ this.analyzeGoSelectorUses(rootNode, relationships, symbols);
1695
+ this.analyzeGoBareIdentifierUses(rootNode, relationships, symbols);
1696
+ for (const s of symbols) {
1697
+ const owner = s.ownerName;
1698
+ if (owner && owner !== s.name) {
1699
+ relationships.push({
1700
+ source: owner,
1701
+ target: s.name,
1702
+ type: ASTEngine.RelationshipType.Contains,
1703
+ confidence: 1,
1704
+ // the member's own declaration line (its name node, so
1705
+ // a wrapped declaration reports where the name is written).
1706
+ ...s.range?.startLine ? { line_number: s.range.startLine } : {},
1707
+ metadata: {}
1708
+ });
1709
+ }
1710
+ }
1711
+ }
1712
+ /**
1713
+ * Go method promotion SHADOWING — the `overrides` relationship.
1714
+ *
1715
+ * WHY THIS IS AN OVERRIDE AND NOT AN INVENTED EDGE. Go has no `override`
1716
+ * keyword, but it has the SEMANTIC: a method declared on the outer type at
1717
+ * depth 0 replaces the promoted method of the same name at depth 1 for every
1718
+ * selector `x.M` (Go spec, selectors: the shallowest depth wins). The family
1719
+ * already models this half — the embedding itself is a `mixes_in` edge
1720
+ * (`go_struct_embedding`: the STRUCT half; the interface half is `extends`).
1721
+ * What was missing is the second half: the analyzer
1722
+ * COMPUTED the shadowing inside `collectGoPromotedMethods` and THREW IT AWAY,
1723
+ * dropping the promoted method from the method set without recording that it
1724
+ * had been displaced. So the relationship was derivable from this file's own
1725
+ * evidence, was never emitted, and the differential could not charge for it —
1726
+ * the exact shape `overrides` was omitted for in bash.
1727
+ *
1728
+ * MEASURED, not ruled — `script/m14/go-ontology-census.ts` probe B2/B3 on
1729
+ * go-stdlib + cobra. See docs/analyzers/go.md for the counts.
1730
+ *
1731
+ * ENDPOINT SPELLING follows the family: a method has no package-scope name of
1732
+ * its own, so the SHADOWING end is `Receiver.Method` (perl, objc, python,
1733
+ * kotlin, dart, swift, php, cpp all publish `overrides|.` as a recorded
1734
+ * separator triple in validation/m60/target-spelling-register.json).
1735
+ *
1736
+ * THE TARGET IS THE MEMBER NAME ALONE (name-surface round, #924). A
1737
+ * relationship target is a NAME the lane publishes, and `Embedder.Method` is a
1738
+ * selector chain — Go has no qualified declaration names. So the promoted
1739
+ * method publishes as its bare name, with `metadata.embedder` and
1740
+ * `metadata.targetQualified` preserving the pair `Embedder.Method` that names
1741
+ * WHICH declaration was displaced. The source keeps its qualified spelling
1742
+ * because a Go method has no package-scope name of its own (the endpoint
1743
+ * ambiguity the fixtures pin); only the target was ever source text on a
1744
+ * name-bearing surface, and this is the rule that built it, fixed at the
1745
+ * source rather than filtered afterwards.
1746
+ *
1747
+ * THE NAME RULE IS GO's, not the family's: the match is on the method NAME
1748
+ * alone. A signature-compatible shadow is the only shape other languages
1749
+ * accept, but Go permits `T.M` and `E.M` to have different signatures, and
1750
+ * `x.M` selects T's either way — so name-only is the fact, and a
1751
+ * signature-matching subset would under-report it. `sameSignature` records
1752
+ * which of the two it is, so a consumer can tell a substitutable shadow from a
1753
+ * hiding one without re-deriving the signature.
1754
+ *
1755
+ * CONFIDENCE 0.95, BELOW 1.0 AND NOT 1.0, with a reason code per the M14
1756
+ * contract : the two declarations are read from this file, but the outer
1757
+ * type's promoted set is resolved against the embedding closure as THIS FILE
1758
+ * declares it. A method promoted from another file of the same package is not
1759
+ * in the closure, so this edge's absence is not evidence of absence — see the
1760
+ * lane's declared limitations.
1761
+ */
1762
+ analyzeGoMethodPromotionShadowing(relationships, symbols) {
1763
+ const embeddedOf = /* @__PURE__ */ Object.create(null);
1764
+ for (const r of relationships) {
1765
+ {
1766
+ const rt = String(r.type || r.relationshipType || "");
1767
+ const constructId = r.metadata?.relationshipType;
1768
+ if (constructId !== "go_struct_embedding" || rt !== ASTEngine.RelationshipType.MixesIn && rt !== ASTEngine.RelationshipType.Extends) {
1769
+ continue;
1770
+ }
1771
+ }
1772
+ const src = String(r.source ?? r.from ?? "");
1773
+ const tgt = String(r.target ?? r.to ?? r.toSymbol ?? "");
1774
+ if (!src || !tgt) continue;
1775
+ const raw = String(r.metadata?.embeddedStruct ?? tgt);
1776
+ if (raw.includes(".")) continue;
1777
+ if (!embeddedOf[src]) embeddedOf[src] = [];
1778
+ if (!embeddedOf[src].includes(tgt)) embeddedOf[src].push(tgt);
1779
+ }
1780
+ if (Object.keys(embeddedOf).length === 0) return;
1781
+ const methods = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Method);
1782
+ const byReceiver = /* @__PURE__ */ Object.create(null);
1783
+ for (const m of methods) {
1784
+ const r = m.metadata?.receiverType || m.metadata?.receiver?.typeName;
1785
+ if (typeof r !== "string" || !r) continue;
1786
+ if (!byReceiver[r]) byReceiver[r] = [];
1787
+ byReceiver[r].push(m);
1788
+ }
1789
+ if (Object.keys(byReceiver).length === 0) return;
1790
+ const depths = /* @__PURE__ */ new Map();
1791
+ for (const m of methods) {
1792
+ const outer = m.metadata?.receiverType || m.metadata?.receiver?.typeName;
1793
+ if (typeof outer !== "string" || !outer) continue;
1794
+ const ownSig = this.normalizeGoSignature(this.buildGoMethodSignature(m));
1795
+ const promoted = (tname, depth, visiting) => {
1796
+ const out = [];
1797
+ if (depth > 4 || visiting.has(tname)) return out;
1798
+ visiting.add(tname);
1799
+ for (const et of embeddedOf[tname] ?? []) {
1800
+ for (const em of byReceiver[et] ?? []) out.push({ m: em, depth: depth + 1 });
1801
+ for (const em of promoted(et, depth + 1, visiting)) out.push(em);
1802
+ }
1803
+ visiting.delete(tname);
1804
+ return out;
1805
+ };
1806
+ for (const entry of promoted(outer, 0, /* @__PURE__ */ new Set())) {
1807
+ const em = entry.m;
1808
+ if (!em || em.name !== m.name) continue;
1809
+ const embedder = em.metadata?.receiverType || em.metadata?.receiver?.typeName;
1810
+ if (typeof embedder !== "string" || !embedder || embedder === outer) continue;
1811
+ const key = `${outer}.${m.name}|${embedder}.${em.name}`;
1812
+ const prior = depths.get(key);
1813
+ if (prior !== void 0 && prior <= entry.depth) continue;
1814
+ depths.set(key, entry.depth);
1815
+ relationships.push({
1816
+ source: `${outer}.${m.name}`,
1817
+ // THE TARGET IS THE MEMBER NAME, NOT A SELECTOR CHAIN (name-surface
1818
+ // round, #924): a relationship target is a NAME the lane publishes,
1819
+ // and `Embedder.Method` is source text with a separator in it — Go has
1820
+ // no qualified declaration names. The SHADOWING declaration keeps its
1821
+ // qualified spelling on the SOURCE (a Go method has no package-scope
1822
+ // name of its own; that is the endpoint-ambiguity rule the fixtures
1823
+ // pin), and the promoting pair moves to `metadata.targetQualified` /
1824
+ // `metadata.embedder` so no fact is lost with the spelling.
1825
+ target: em.name,
1826
+ type: ASTEngine.RelationshipType.Overrides,
1827
+ confidence: 0.95,
1828
+ // the SHADOWING declaration's own line — the site that
1829
+ // makes `x.M` select this method rather than the promoted one.
1830
+ ...m.range?.startLine ? { line_number: m.range.startLine } : {},
1831
+ metadata: {
1832
+ relationshipType: "go_method_promotion_shadowing",
1833
+ isOverride: true,
1834
+ // THE PROMOTING TYPE, and the pair spelling it used to publish as the
1835
+ // TARGET before the name-surface round. The target is now the member
1836
+ // name alone, so these two fields carry the identity that distinguishes
1837
+ // two embedders declaring the same method name.
1838
+ embedder,
1839
+ targetQualified: `${embedder}.${em.name}`,
1840
+ // contract : confidence < 1.0 carries a machine-readable reason.
1841
+ reasonCode: "promoted-method-shadowed-by-outer-declaration",
1842
+ // True when the two signatures match, i.e. this is a substitutable
1843
+ // override; false when the outer method merely HIDES the promoted one
1844
+ // (legal in Go, and still the method `x.M` selects).
1845
+ sameSignature: this.normalizeGoSignature(this.buildGoMethodSignature(em)) === ownSig,
1846
+ shadowedAtDepth: 0,
1847
+ promotedAtDepth: entry.depth
1848
+ }
1849
+ });
1850
+ }
1851
+ }
1852
+ }
1853
+ /**
1854
+ * Go Struct Embedding Analysis
1855
+ * Analyzes struct embedding (anonymous fields) which is Go's composition pattern
1856
+ */
1857
+ analyzeGoStructEmbedding(rootNode, relationships) {
1858
+ const stack = [rootNode];
1859
+ while (stack.length > 0) {
1860
+ const node = stack.pop();
1861
+ if (node.type === "type_declaration") {
1862
+ const typeSpecs = [];
1863
+ for (let ti = 0; ti < node.namedChildCount; ti++) {
1864
+ const ts = node.namedChild(ti);
1865
+ if (ts?.type === "type_spec" || ts?.type === "type_alias") typeSpecs.push(ts);
1866
+ }
1867
+ for (const typeSpec of typeSpecs) {
1868
+ const nameNode = typeSpec.childForFieldName("name");
1869
+ const typeName = nameNode?.text;
1870
+ if (!typeName) continue;
1871
+ const structSpec = this.findChildByType(typeSpec, "struct_type");
1872
+ if (structSpec) {
1873
+ const embeddedTypeNodes = this.extractGoEmbeddedTypeNodes(structSpec);
1874
+ forEachSymbol(embeddedTypeNodes, (embeddedType) => {
1875
+ const rawEmbedded = String(embeddedType.text);
1876
+ const embedLeaf = this.goEmbeddedFieldName(embeddedType);
1877
+ if (!embedLeaf) return;
1878
+ relationships.push({
1879
+ source: typeName,
1880
+ target: embedLeaf,
1881
+ // THE RELABEL: stored type `extends` -> `mixes_in`, with the
1882
+ // confidence (0.8) and therefore the evidenceClass UNCHANGED —
1883
+ // that is the relabel contract, and it is what keeps this a
1884
+ // label move rather than a distribution change.
1885
+ type: ASTEngine.RelationshipType.MixesIn,
1886
+ confidence: 0.8,
1887
+ // the EMBEDDED TYPE's line — the site the
1888
+ // embedded declaration is written on.
1889
+ ...this.nodeLine(embeddedType) ? { line_number: this.nodeLine(embeddedType) } : {},
1890
+ metadata: {
1891
+ // THE CONSTRUCT ID IS KEPT: `go_struct_embedding` names the
1892
+ // construct and is what every consumer (including this
1893
+ // analyzer's own two passes) keys on, so the label move does
1894
+ // not need a metadata move as well.
1895
+ relationshipType: "go_struct_embedding",
1896
+ isEmbedding: true,
1897
+ isAnonymous: true,
1898
+ isMixin: true,
1899
+ embedType: "struct_composition_mixes_in",
1900
+ embeddedStruct: rawEmbedded
1901
+ }
1902
+ });
1903
+ });
1904
+ }
1905
+ const interfaceSpec = this.findChildByType(typeSpec, "interface_type");
1906
+ if (interfaceSpec) {
1907
+ for (let j = 0; j < interfaceSpec.namedChildCount; j++) {
1908
+ const elem = interfaceSpec.namedChild(j);
1909
+ if (!elem || elem.type !== "constraint_elem") continue;
1910
+ const inner = elem.namedChildCount > 0 ? elem.namedChild(0) : void 0;
1911
+ if (!inner) continue;
1912
+ if (inner.type !== "type_identifier" && inner.type !== "qualified_type") continue;
1913
+ const raw = inner.text;
1914
+ const leaf = raw.lastIndexOf(".") !== -1 ? raw.slice(raw.lastIndexOf(".") + 1) : raw;
1915
+ if (!leaf || !this.isPlainGoIdentifier(leaf)) continue;
1916
+ relationships.push({
1917
+ source: typeName,
1918
+ target: leaf,
1919
+ type: ASTEngine.RelationshipType.Extends,
1920
+ confidence: 0.8,
1921
+ // the embedded interface reference's own line.
1922
+ ...this.nodeLine(inner) ? { line_number: this.nodeLine(inner) } : {},
1923
+ metadata: {
1924
+ relationshipType: "go_interface_embedding",
1925
+ isEmbedding: true,
1926
+ isAnonymous: true,
1927
+ embedType: "interface_inheritance_extends",
1928
+ embeddedInterface: raw
1929
+ }
1930
+ });
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
1936
+ const child = node.namedChild(i);
1937
+ if (child) stack.push(child);
1938
+ }
1939
+ }
1940
+ }
1941
+ /**
1942
+ * RENDERS — Go's render host is the TEMPLATE EXECUTION, and the rendered
1943
+ * component must be a PUBLISHED SYMBOL.
1944
+ *
1945
+ * WHY THIS IS A RELATION AND NOT A CALL. `pageTemplate.Execute(w, data)` is a
1946
+ * method call and the `calls` pass already publishes it; `renders` asserts the
1947
+ * DIFFERENT fact the call performs: this symbol puts THIS template's output on
1948
+ * the wire. The family's renders contract is "the symbol whose body creates
1949
+ * the rendered component renders it" (jsx/tsx; dart's build method returning a
1950
+ * Widget), and Go's standard-library equivalent is `html/template` execution —
1951
+ * the template IS the rendered component. The call edge stays: it is a real
1952
+ * invocation, and one site can honestly carry both facts.
1953
+ *
1954
+ * THE RENDER HOST FILTER IS THE IMPORT, NOT THE NAME. The file must import
1955
+ * `html/template`, the HTML renderer. `text/template` is the measured IMPOSTOR
1956
+ * class — 151 of the corpus's template-importing files use it and almost all
1957
+ * are CODE GENERATORS, not renderers — so a text/template-only file publishes
1958
+ * no renders row, and a file that aliases text/template as a SECOND import
1959
+ * cannot smuggle one in: the constructor's QUALIFIER is resolved against the
1960
+ * html/template import's own local name, never against the spelling
1961
+ * "template". A dot-imported (`import . "html/template"`) constructor cannot
1962
+ * be qualified, so it publishes no row — declared MUST-NOT, not a guess.
1963
+ *
1964
+ * THE TARGET MUST BE A PUBLISHED SYMBOL. A template value is a component only
1965
+ * when the program NAMES it: a package-level var whose initializer constructs
1966
+ * a template (`var page = template.Must(template.New("page").Parse(...))`).
1967
+ * A local template (`tpl := template.Must(...)`), a parameter, a struct field,
1968
+ * a call result or an expression receiver publishes NO row: Go locals are not
1969
+ * symbols (LocalsAreNotSymbols), and a NAME STRING would let the arbiter bind
1970
+ * an unrelated declaration — the false-edge direction this programme refuses.
1971
+ * Cross-file execution is therefore silent too: a per-file analyzer cannot
1972
+ * prove that a bare identifier from ANOTHER file of the package is a template,
1973
+ * and the limitation is declared rather than fabricated around.
1974
+ *
1975
+ * LINE: the OPERAND's line — the rendered symbol's own name at the call site.
1976
+ * A method selector can wrap (`pageTemplate.\n\tExecute(...)`), so the Execute
1977
+ * token's line is a different, wrong site (a row's line is the
1978
+ * name-bearing node's line).
1979
+ *
1980
+ * CONFIDENCE 0.9 / usage_contextual, the render family's tier: both ends are
1981
+ * read from this file, but the template's identity is recognised from its
1982
+ * CONSTRUCTOR CALL rather than a declared type (tree-sitter-go has no type
1983
+ * resolution), so the row is not `structural`.
1984
+ */
1985
+ analyzeGoTemplateRenders(rootNode, imports, relationships) {
1986
+ let hostName;
1987
+ let dotImported = false;
1988
+ for (const imp of imports) {
1989
+ if ((imp.module ?? "") !== "html/template") continue;
1990
+ const alias = typeof imp.alias === "string" ? String(imp.alias) : "";
1991
+ if (alias === "_") continue;
1992
+ if (alias === ".") {
1993
+ dotImported = true;
1994
+ continue;
1995
+ }
1996
+ hostName = alias.length > 0 ? alias : "template";
1997
+ break;
1998
+ }
1999
+ if (!hostName || dotImported) return;
2000
+ const templateSymbols = /* @__PURE__ */ new Map();
2001
+ for (let i = 0; i < rootNode.namedChildCount; i++) {
2002
+ const decl = rootNode.namedChild(i);
2003
+ if (!decl || decl.type !== "var_declaration") continue;
2004
+ for (let j = 0; j < decl.namedChildCount; j++) {
2005
+ const spec = decl.namedChild(j);
2006
+ if (!spec || spec.type !== "var_spec") continue;
2007
+ const value = spec.childForFieldName?.("value");
2008
+ if (!value) continue;
2009
+ const constructed = this.goTemplateConstructionName(value, hostName);
2010
+ if (constructed === null) continue;
2011
+ for (let k = 0; k < spec.namedChildCount; k++) {
2012
+ const c = spec.namedChild(k);
2013
+ if (!c || c.type !== "identifier") continue;
2014
+ const text = String(c.text ?? "");
2015
+ if (text.length === 0 || text === "_") continue;
2016
+ templateSymbols.set(text, constructed);
2017
+ }
2018
+ }
2019
+ }
2020
+ if (templateSymbols.size === 0) return;
2021
+ const seen = /* @__PURE__ */ new Set();
2022
+ const stack = [rootNode];
2023
+ while (stack.length > 0) {
2024
+ const node = stack.pop();
2025
+ if (node.type === "call_expression") {
2026
+ const fn = node.childForFieldName("function");
2027
+ if (fn && fn.type === "selector_expression") {
2028
+ const field = fn.childForFieldName("field");
2029
+ const operand = fn.childForFieldName("operand");
2030
+ const method = field ? String(field.text ?? "") : "";
2031
+ if ((method === "Execute" || method === "ExecuteTemplate") && operand && operand.type === "identifier") {
2032
+ const target = String(operand.text ?? "");
2033
+ const constructedName = templateSymbols.get(target);
2034
+ if (constructedName !== void 0) {
2035
+ const source = this.findContainingGoFunction(node);
2036
+ const key = `${source ?? ""}|${target}`;
2037
+ if (source && !seen.has(key)) {
2038
+ seen.add(key);
2039
+ const templateName = method === "ExecuteTemplate" ? this.goTemplateRenderNameArgument(node, constructedName) : constructedName;
2040
+ relationships.push({
2041
+ source,
2042
+ target,
2043
+ type: ASTEngine.RelationshipType.Renders,
2044
+ confidence: 0.9,
2045
+ ...this.nodeLine(operand) ? { line_number: this.nodeLine(operand) } : {},
2046
+ metadata: {
2047
+ relationshipType: "go_template_render",
2048
+ isRenderHost: true,
2049
+ renderHost: "html/template",
2050
+ renderMethod: method,
2051
+ ...templateName ? { templateName } : {}
2052
+ }
2053
+ });
2054
+ }
2055
+ }
2056
+ }
2057
+ }
2058
+ }
2059
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2060
+ const child = node.namedChild(i);
2061
+ if (child) stack.push(child);
2062
+ }
2063
+ }
2064
+ }
2065
+ /**
2066
+ * Does this initializer construct an `html/template` through the import's own
2067
+ * local name? `null` when it does not construct a template at all; the
2068
+ * template's own `template.New("<literal>")` name when it does; `""` when it
2069
+ * constructs one whose name is a runtime value.
2070
+ *
2071
+ * Recognised root constructors are the standard library's own: Must, New,
2072
+ * ParseFiles, ParseGlob, ParseFS. `Parse` is deliberately NOT a root
2073
+ * constructor: it is a METHOD on a template (`tpl.Parse(...)`), so an
2074
+ * initializer whose root call is `Parse` is not constructing one; the
2075
+ * `template.Must(template.New(...).Parse(...))` chain is recognised through
2076
+ * Must and its arguments are searched for the `New` literal.
2077
+ */
2078
+ goTemplateConstructionName(node, hostName) {
2079
+ const constructors = ["Must", "New", "ParseFiles", "ParseGlob", "ParseFS"];
2080
+ const asHostCall = (n) => {
2081
+ if (n.type !== "call_expression") return null;
2082
+ const fn = n.childForFieldName("function");
2083
+ if (!fn || fn.type !== "selector_expression") return null;
2084
+ const operand = fn.childForFieldName("operand");
2085
+ const field = fn.childForFieldName("field");
2086
+ if (!operand || operand.type !== "identifier") return null;
2087
+ if (String(operand.text ?? "") !== hostName) return null;
2088
+ if (!field || !inArray(constructors, String(field.text ?? ""))) return null;
2089
+ return n;
2090
+ };
2091
+ const nameOfNew = (call) => {
2092
+ const args = call.childForFieldName("arguments");
2093
+ const first = args?.namedChild(0);
2094
+ if (!first || first.type !== "interpreted_string_literal") return "";
2095
+ return this.stripQuotedString(String(first.text ?? ""));
2096
+ };
2097
+ const found = (n, depth) => {
2098
+ if (depth > 6) return null;
2099
+ const root = asHostCall(n);
2100
+ if (root) {
2101
+ const field = root.childForFieldName("function")?.childForFieldName("field");
2102
+ if (String(field?.text ?? "") === "New") return nameOfNew(root);
2103
+ const args = root.childForFieldName("arguments");
2104
+ for (const a of args?.namedChildren ?? []) {
2105
+ if (!a) continue;
2106
+ const r = found(a, depth + 1);
2107
+ if (r !== null && r.length > 0) return r;
2108
+ }
2109
+ return "";
2110
+ }
2111
+ let sawConstruction = false;
2112
+ for (let i = 0; i < n.namedChildCount; i++) {
2113
+ const c = n.namedChild(i);
2114
+ if (!c) continue;
2115
+ const r = found(c, depth + 1);
2116
+ if (r !== null) {
2117
+ sawConstruction = true;
2118
+ if (r.length > 0) return r;
2119
+ }
2120
+ }
2121
+ if (!sawConstruction) return null;
2122
+ return "";
2123
+ };
2124
+ return found(node, 0);
2125
+ }
2126
+ /**
2127
+ * The rendered sub-template's name, for `ExecuteTemplate(w, "name", data)`:
2128
+ * its SECOND argument when that argument is a string literal; otherwise the
2129
+ * template's own construction name. `undefined` when neither names it — no
2130
+ * NAME is ever invented for metadata.
2131
+ */
2132
+ goTemplateRenderNameArgument(call, constructedName) {
2133
+ const args = call.childForFieldName("arguments");
2134
+ const second = args?.namedChild(1);
2135
+ if (second && second.type === "interpreted_string_literal") {
2136
+ const name = this.stripQuotedString(String(second.text ?? ""));
2137
+ if (name.length > 0) return name;
2138
+ }
2139
+ return constructedName.length > 0 ? constructedName : void 0;
2140
+ }
2141
+ /**
2142
+ * Go Interface Implementation Analysis
2143
+ * Analyzes implicit interface implementation (Go's duck typing)
2144
+ */
2145
+ analyzeGoInterfaceImplementation(rootNode, relationships, symbols) {
2146
+ const interfaces = filterSymbols(symbols, (s) => s.metadata?.isInterface);
2147
+ const structs = filterSymbols(
2148
+ symbols,
2149
+ (s) => (s.kind === ASTEngine.SymbolKind.Struct || s.kind === ASTEngine.SymbolKind.Type) && !s.metadata?.isInterface
2150
+ );
2151
+ const methods = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Method);
2152
+ const declaredTypeNames = /* @__PURE__ */ new Set();
2153
+ for (const s of symbols) {
2154
+ if (s.kind === ASTEngine.SymbolKind.Struct || s.kind === ASTEngine.SymbolKind.Interface || s.kind === ASTEngine.SymbolKind.Type) {
2155
+ declaredTypeNames.add(s.name);
2156
+ }
2157
+ }
2158
+ const candidates = mapSymbols(structs, (s) => ({
2159
+ name: s.name,
2160
+ hasGenerics: !!s.metadata?.hasGenerics
2161
+ }));
2162
+ for (const m of methods) {
2163
+ const receiverType = m.metadata?.receiverType || m.metadata?.receiver?.typeName;
2164
+ if (typeof receiverType !== "string" || !receiverType) continue;
2165
+ if (declaredTypeNames.has(receiverType)) continue;
2166
+ if (someSymbol(candidates, (c) => c.name === receiverType)) continue;
2167
+ candidates.push({ name: receiverType, hasGenerics: false });
2168
+ }
2169
+ const embeddedOf = /* @__PURE__ */ Object.create(null);
2170
+ for (const r of relationships) {
2171
+ {
2172
+ const rt = String(r.type || r.relationshipType || "");
2173
+ const constructId = r.metadata?.relationshipType;
2174
+ if (constructId !== "go_struct_embedding" || rt !== ASTEngine.RelationshipType.MixesIn && rt !== ASTEngine.RelationshipType.Extends) {
2175
+ continue;
2176
+ }
2177
+ const src = String(r.source ?? r.from ?? "");
2178
+ const tgt = String(r.target ?? r.to ?? r.toSymbol ?? "");
2179
+ if (src && tgt) {
2180
+ if (!embeddedOf[src]) embeddedOf[src] = [];
2181
+ embeddedOf[src].push(tgt);
2182
+ }
2183
+ }
2184
+ }
2185
+ const promotedByCandidate = /* @__PURE__ */ new Map();
2186
+ forEachSymbol(candidates, (candidate) => {
2187
+ promotedByCandidate.set(
2188
+ candidate.name,
2189
+ this.collectGoPromotedMethods(candidate.name, methods, symbols, embeddedOf, /* @__PURE__ */ new Set())
2190
+ );
2191
+ });
2192
+ forEachSymbol(interfaces, (interfaceSymbol) => {
2193
+ if (interfaceSymbol.metadata?.hasGenerics) return;
2194
+ const interfaceMethods = this.getGoInterfaceMethods(interfaceSymbol, symbols);
2195
+ forEachSymbol(candidates, (candidate) => {
2196
+ if (candidate.hasGenerics) return;
2197
+ const structName = candidate.name;
2198
+ if (structName === interfaceSymbol.name) return;
2199
+ const promoted = promotedByCandidate.get(structName) ?? [];
2200
+ const valueMethods = [
2201
+ ...filterSymbols(methods, (m) => {
2202
+ const receiverType = m.metadata?.receiverType || m.metadata?.receiver?.typeName;
2203
+ const isPointer = !!(m.metadata?.isPointerReceiver || m.metadata?.receiver?.isPointer);
2204
+ return receiverType === structName && !isPointer;
2205
+ }),
2206
+ ...promoted
2207
+ ];
2208
+ const pointerMethods = [
2209
+ ...filterSymbols(methods, (m) => {
2210
+ const receiverType = m.metadata?.receiverType || m.metadata?.receiver?.typeName;
2211
+ return receiverType === structName;
2212
+ }),
2213
+ ...promoted
2214
+ ];
2215
+ const valueImpl = this.checkGoInterfaceImplementation(interfaceMethods, valueMethods);
2216
+ const pointerImpl = this.checkGoInterfaceImplementation(interfaceMethods, pointerMethods);
2217
+ if (valueImpl.implements) {
2218
+ relationships.push({
2219
+ source: structName,
2220
+ target: interfaceSymbol.name,
2221
+ type: ASTEngine.RelationshipType.Implements,
2222
+ confidence: valueImpl.confidence,
2223
+ // the interface declaration's own line — the site
2224
+ // that NAMES the target (the struct's line would name the source).
2225
+ ...interfaceSymbol.range?.startLine ? { line_number: interfaceSymbol.range.startLine } : {},
2226
+ metadata: {
2227
+ relationshipType: "go_interface_implementation",
2228
+ isImplicit: true,
2229
+ matchedMethods: valueImpl.matchedMethods,
2230
+ implementationType: "duck_typing",
2231
+ receiverSet: "value"
2232
+ }
2233
+ });
2234
+ }
2235
+ if (!valueImpl.implements && pointerImpl.implements) {
2236
+ relationships.push({
2237
+ source: structName,
2238
+ target: interfaceSymbol.name,
2239
+ type: ASTEngine.RelationshipType.Implements,
2240
+ confidence: pointerImpl.confidence,
2241
+ ...interfaceSymbol.range?.startLine ? { line_number: interfaceSymbol.range.startLine } : {},
2242
+ metadata: {
2243
+ relationshipType: "go_interface_implementation",
2244
+ isImplicit: true,
2245
+ matchedMethods: pointerImpl.matchedMethods,
2246
+ implementationType: "duck_typing",
2247
+ receiverSet: "pointer"
2248
+ }
2249
+ });
2250
+ }
2251
+ });
2252
+ });
2253
+ }
2254
+ /**
2255
+ * Go embedded-struct method promotion (Go spec, promoted methods).
2256
+ * Returns the method-symbol set reachable on `structName` through embedded
2257
+ * structs (the go_struct_embedding closure built in
2258
+ * analyzeGoInterfaceImplementation). `worker struct { *shape; name string }`
2259
+ * promotes shape's methods into worker's method set, so worker satisfies
2260
+ * any interface shape satisfies. Transitive and cycle-safe. NOTE: deduped
2261
+ * by method signature (name+params+return); a struct defining its own
2262
+ * method with the same signature as a promoted one shadows the promotion
2263
+ * (Go rule: a promoted method at shallower depth wins / is shadowed by an
2264
+ * explicit method).
2265
+ */
2266
+ collectGoPromotedMethods(structName, methods, symbols, embeddedOf, visiting) {
2267
+ if (visiting.has(structName)) return [];
2268
+ visiting.add(structName);
2269
+ const acc = [];
2270
+ const embeddedTypes = embeddedOf[structName];
2271
+ if (embeddedTypes) {
2272
+ for (const et of embeddedTypes) {
2273
+ const embeddedMethods = filterSymbols(
2274
+ methods,
2275
+ (m) => (m.metadata?.receiverType || m.metadata?.receiver?.typeName) === et
2276
+ );
2277
+ const ownSig = (m) => this.buildGoMethodSignature(m);
2278
+ forEachSymbol(embeddedMethods, (m) => {
2279
+ const shadowed = someSymbol(
2280
+ [...filterSymbols(methods, (sm) => (sm.metadata?.receiverType || sm.metadata?.receiver?.typeName) === structName), ...acc],
2281
+ (x) => x !== m && this.buildGoMethodSignature(x) === ownSig(m)
2282
+ );
2283
+ if (!shadowed && !someSymbol(acc, (x) => this.buildGoMethodSignature(x) === ownSig(m))) {
2284
+ acc.push(m);
2285
+ }
2286
+ });
2287
+ const transitive = this.collectGoPromotedMethods(et, methods, symbols, embeddedOf, visiting);
2288
+ forEachSymbol(transitive, (m) => {
2289
+ const shadowed = someSymbol(
2290
+ [...filterSymbols(methods, (sm) => (sm.metadata?.receiverType || sm.metadata?.receiver?.typeName) === structName), ...acc],
2291
+ (x) => x !== m && this.buildGoMethodSignature(x) === this.buildGoMethodSignature(m)
2292
+ );
2293
+ if (!shadowed && !someSymbol(acc, (x) => this.buildGoMethodSignature(x) === this.buildGoMethodSignature(m))) {
2294
+ acc.push(m);
2295
+ }
2296
+ });
2297
+ }
2298
+ }
2299
+ visiting.delete(structName);
2300
+ return acc;
2301
+ }
2302
+ /**
2303
+ * THE FILE'S STDLIB PACKAGE QUALIFIERS: the spelling a call site may use for an imported stdlib
2304
+ * package -> that package's import path.
2305
+ *
2306
+ * Go lets a file qualify a package member by the import's LOCAL NAME: the alias when written
2307
+ * (`j "encoding/json"`), otherwise the package's own name, which for the standard library is the
2308
+ * last path segment except for the few packages whose name differs from their directory (and
2309
+ * those are aliased by every consumer that needs the distinction). `_` and `.` imports qualify
2310
+ * nothing and are skipped; two distinct modules claiming one qualifier cannot be distinguished at
2311
+ * a call site, so the qualifier is dropped rather than guessed.
2312
+ */
2313
+ collectGoStdlibQualifiers(imports) {
2314
+ const out = /* @__PURE__ */ new Map();
2315
+ const conflicted = /* @__PURE__ */ new Set();
2316
+ for (const imp of imports) {
2317
+ const module = String(imp.module ?? "");
2318
+ if (module.length === 0 || !isGoStdlibModulePath(module)) continue;
2319
+ const alias = typeof imp.alias === "string" ? imp.alias : "";
2320
+ if (alias === "_" || alias === ".") continue;
2321
+ const packageName = String(imp.metadata?.packageName ?? this.getGoPackageName(module));
2322
+ const qualifier = alias.length > 0 ? alias : packageName;
2323
+ if (qualifier.length === 0) continue;
2324
+ if (out.has(qualifier) && out.get(qualifier) !== module) {
2325
+ conflicted.add(qualifier);
2326
+ continue;
2327
+ }
2328
+ if (!conflicted.has(qualifier)) out.set(qualifier, module);
2329
+ }
2330
+ for (const q of conflicted) out.delete(q);
2331
+ return out;
2332
+ }
2333
+ /**
2334
+ * THE ONE CLAIM A LEAF CALL TARGET CAN CARRY — `isStdlib` when the call is a DIRECT member of a
2335
+ * standard-library package this file imports.
2336
+ *
2337
+ * WHY THIS IS THE CHANNEL. The gate reads `metadata.reasonCode` nowhere; the claims it reads are
2338
+ * `EXTERNAL_KEYS` (verified against the key space) and `baseBeyondRunScope` (verified by spelling
2339
+ * + owner consistency). This lane's unresolved calls are BARE LEAVES (`fmt.Println` publishes
2340
+ * `Println`), and `targetOwner("Println")` is null, so a declared cause can never be attached to
2341
+ * one: measured over the 400-file stride, all 24,385 unresolved edges read `causeRefuted` when
2342
+ * their own reason is placed on that key. The external channel is different by construction — a
2343
+ * bare identifier is looked up directly and, because the claim is read only where the target names
2344
+ * nothing, the lookup returns 0 and the claim verifies without ever being able to manufacture a
2345
+ * refutation. What makes it HONEST is the proof in front of it, not the arbiter: the qualifier is
2346
+ * a package the file itself imports, and the import path's first segment is a member of the Go
2347
+ * standard library's closed top-level set. A receiver variable (`r.reader.SearchWhere`), a local
2348
+ * shadow of an import name, a project-local package and a third-party module all fail that proof
2349
+ * and are left silent.
2350
+ *
2351
+ * WHAT IT DOES NOT DO. It never raises `resolved`, it does not change the emitted target, and it
2352
+ * does not claim anything about a leaf this FILE publishes (then the edge is not external at the
2353
+ * per-file scope the analyzer actually knows). Returns the import path when the claim is true.
2354
+ */
2355
+ goStdlibCallClaim(callExpression, leaf, symbols, qualifiers, ownPackageName) {
2356
+ const firstDot = callExpression.indexOf(".");
2357
+ if (firstDot <= 0) return null;
2358
+ if (callExpression.indexOf(".", firstDot + 1) !== -1) return null;
2359
+ const qualifier = callExpression.slice(0, firstDot);
2360
+ const module = qualifiers.get(qualifier);
2361
+ if (module === void 0) return null;
2362
+ if (qualifier === ownPackageName) return null;
2363
+ if (findSymbol(symbols, (s) => s.name === qualifier)) return null;
2364
+ if (findSymbol(symbols, (s) => this.isGoPackageCallable(s) && s.name === leaf)) return null;
2365
+ return module;
2366
+ }
2367
+ /**
2368
+ * THE SAME PROOF FOR A QUALIFIED TYPE SPELLING — the shared shape behind the `references` and
2369
+ * `instantiates` claims: `context.Context`, `http.Server`, `time.Duration`.
2370
+ *
2371
+ * The differences from `goStdlibCallClaim` are deliberate and are the two ways a type use differs
2372
+ * from a call: the leaf is a TYPE, so any declaration of that name in this file is enough to stop
2373
+ * the claim (a call needs a callable); and a qualified spelling is held for the USE even when the
2374
+ * file also declares the leaf, which is exactly the case where the edge may bind to the wrong
2375
+ * declaration — so the guard matters more here, not less. A deeper chain (`pkg.Sub.Type`) is not
2376
+ * a direct package member and is refused, as on the call surface.
2377
+ */
2378
+ goStdlibTypeClaim(qualified, leaf, symbols, qualifiers, ownPackageName) {
2379
+ const firstDot = qualified.indexOf(".");
2380
+ if (firstDot <= 0) return null;
2381
+ if (qualified.indexOf(".", firstDot + 1) !== -1) return null;
2382
+ const qualifier = qualified.slice(0, firstDot);
2383
+ const module = qualifiers.get(qualifier);
2384
+ if (module === void 0) return null;
2385
+ if (qualifier === ownPackageName) return null;
2386
+ if (findSymbol(symbols, (s) => s.name === qualifier)) return null;
2387
+ if (findSymbol(symbols, (s) => s.name === leaf)) return null;
2388
+ return module;
2389
+ }
2390
+ /**
2391
+ * Go Function Call Analysis
2392
+ * Analyzes Go function calls including package-qualified calls.
2393
+ * Family contract (Rust M12.18): calls emit LEAF targets — a receiver chain
2394
+ * `s.repo.Save(user)` emits `calls Save -> Save` exactly like Rust's
2395
+ * `self.repository.find(id)` emits `calls find -> find`, and stdlib calls
2396
+ * (`strings.ToUpper`) emit `calls transform -> ToUpper` like Rust's
2397
+ * `calls normalize -> println`. Instantiates fires ONLY for PascalCase
2398
+ * top-level FUNCTION symbols (Go constructor convention `NewXxx`) — never
2399
+ * for methods or unresolved stdlib leaves.
2400
+ */
2401
+ analyzeGoFunctionCalls(rootNode, relationships, symbols, stdlibQualifiers) {
2402
+ const functions = filterSymbols(
2403
+ symbols,
2404
+ (s) => s.kind === ASTEngine.SymbolKind.Function || s.kind === ASTEngine.SymbolKind.Method
2405
+ );
2406
+ const dotImported = this.hasGoDotImport(rootNode);
2407
+ const ownPackageName = this.goOwnPackageName(rootNode);
2408
+ forEachSymbol(functions, (func) => {
2409
+ const qualifiedCalls = func.qualified_calls || [];
2410
+ const callSites = this.goCallSites.get(func.range?.startIndex ?? -1);
2411
+ forEachSymbol(qualifiedCalls, (call) => {
2412
+ const callLine = this.nodeLine(callSites?.get(call));
2413
+ const targetFunction = this.resolveGoFunctionCall(call, symbols, dotImported);
2414
+ if (targetFunction && this.isGoBuiltin(targetFunction)) {
2415
+ return;
2416
+ }
2417
+ if (targetFunction) {
2418
+ const stdlibModule = this.goStdlibCallClaim(call, targetFunction, symbols, stdlibQualifiers, ownPackageName);
2419
+ relationships.push({
2420
+ source: func.name,
2421
+ target: targetFunction,
2422
+ type: ASTEngine.RelationshipType.Calls,
2423
+ confidence: 0.8,
2424
+ ...callLine ? { line_number: callLine } : {},
2425
+ metadata: {
2426
+ relationshipType: "go_function_call",
2427
+ callExpression: call,
2428
+ isPackageQualified: stringIncludes(call, "."),
2429
+ // `isStdlib` is one of the gate's recognised EXTERNAL_KEYS, and the PROOF is the
2430
+ // import written in this file plus the closed top-level stdlib set (`stdlibPath` is
2431
+ // the evidence, kept beside the claim for a reviewer; it is not a claim key).
2432
+ ...stdlibModule !== null ? { isStdlib: true, stdlibPath: stdlibModule } : {}
2433
+ }
2434
+ });
2435
+ const ctorSymbol = findSymbol(
2436
+ symbols,
2437
+ (s) => s.name === targetFunction && s.kind === ASTEngine.SymbolKind.Function && !s.ownerName
2438
+ );
2439
+ const calleeLeaf = callSites?.get(call);
2440
+ const calleeNode = calleeLeaf && calleeLeaf.parent?.type === "selector_expression" ? calleeLeaf.parent : calleeLeaf;
2441
+ if (ctorSymbol && this.isGoTopLevelFunctionCallee(calleeNode, ownPackageName) && ctorSymbol.name[0] === ctorSymbol.name[0].toUpperCase() && ctorSymbol.name[0] !== ctorSymbol.name[0].toLowerCase()) {
2442
+ relationships.push({
2443
+ source: func.name,
2444
+ target: targetFunction,
2445
+ type: ASTEngine.RelationshipType.Instantiates,
2446
+ confidence: 0.6,
2447
+ ...callLine ? { line_number: callLine } : {},
2448
+ metadata: { relationshipType: "go_instantiation", callExpression: call }
2449
+ });
2450
+ }
2451
+ }
2452
+ });
2453
+ });
2454
+ }
2455
+ /**
2456
+ * Package-scope call analysis. Captures call_expression nodes that sit at
2457
+ * package level (var/const initializers, top-level assignments) — e.g.
2458
+ * `var _ = intVar(123)`, `var r = regexp.MustCompile(...)`, or
2459
+ * `keys.NewString("view_type", "")`. findGoFunctionCalls only walks
2460
+ * function/method bodies, so these real calls were NEVER emitted (Go wave
2461
+ * 2026-08-28: newexpr.go, session.go NewString, semtok.go MustCompile all
2462
+ * leaked as differential MISSING). Emitted with source = package/"global"
2463
+ * (no enclosing function). Mirrors resolveGoFunctionCall: bare callee emits
2464
+ * only if it names an in-file declared function/method; qualified callee
2465
+ * emits the leaf unconditionally; builtins and func-literals skipped.
2466
+ */
2467
+ analyzeGoPackageScopeCalls(rootNode, relationships, symbols, stdlibQualifiers) {
2468
+ const declared = /* @__PURE__ */ new Set();
2469
+ const dotImported = this.hasGoDotImport(rootNode);
2470
+ const ownPackageName = this.goOwnPackageName(rootNode);
2471
+ const ctorFns = /* @__PURE__ */ new Set();
2472
+ for (const s of symbols) {
2473
+ if (this.isGoPackageCallable(s)) {
2474
+ declared.add(s.name);
2475
+ }
2476
+ if (s.kind === ASTEngine.SymbolKind.Function && !s.ownerName) {
2477
+ ctorFns.add(s.name);
2478
+ }
2479
+ }
2480
+ const stack = [rootNode];
2481
+ const emitted = /* @__PURE__ */ new Set();
2482
+ while (stack.length > 0) {
2483
+ const node = stack.pop();
2484
+ if (node.type === "function_declaration" || node.type === "method_declaration") continue;
2485
+ if (node.type === "func_literal" && node.parent?.type !== "call_expression") continue;
2486
+ if (node.type === "call_expression") {
2487
+ const fn = node.childForFieldName("function");
2488
+ if (fn) {
2489
+ const leaf = this.resolveGoCallLeaf(fn);
2490
+ if (fn.type === "func_literal") {
2491
+ for (let i = fn.namedChildCount - 1; i >= 0; i--) {
2492
+ const fc = fn.namedChild(i);
2493
+ if (fc) stack.push(fc);
2494
+ }
2495
+ continue;
2496
+ }
2497
+ if (!leaf) {
2498
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2499
+ const child = node.namedChild(i);
2500
+ if (child) stack.push(child);
2501
+ }
2502
+ continue;
2503
+ }
2504
+ const qualified = fn.type === "selector_expression";
2505
+ const isCtor = leaf[0] === leaf[0].toUpperCase() && leaf[0] !== leaf[0].toLowerCase();
2506
+ const emit = !this.isGoBuiltin(leaf) && (qualified || dotImported || declared.has(leaf));
2507
+ const callSrc = this.resolveGoWorkSource(node);
2508
+ const callKey = `call:${callSrc}:${leaf}`;
2509
+ if (emit && !emitted.has(callKey)) {
2510
+ emitted.add(callKey);
2511
+ const stdlibModule = this.goStdlibCallClaim(fn.text, leaf, symbols, stdlibQualifiers, ownPackageName);
2512
+ relationships.push({
2513
+ source: callSrc,
2514
+ target: leaf,
2515
+ type: ASTEngine.RelationshipType.Calls,
2516
+ confidence: 0.8,
2517
+ // the package-scope walk holds the callee node
2518
+ // directly — its LEAF's line, not the statement's.
2519
+ ...this.nodeLine(this.goCalleeNameNode(fn)) ? { line_number: this.nodeLine(this.goCalleeNameNode(fn)) } : {},
2520
+ metadata: {
2521
+ relationshipType: "go_package_scope_call",
2522
+ callExpression: fn.text,
2523
+ isPackageQualified: qualified,
2524
+ ...stdlibModule !== null ? { isStdlib: true, stdlibPath: stdlibModule } : {}
2525
+ }
2526
+ });
2527
+ }
2528
+ if (isCtor && ctorFns.has(leaf) && this.isGoTopLevelFunctionCallee(fn, ownPackageName) && !emitted.has("inst:" + leaf)) {
2529
+ emitted.add("inst:" + leaf);
2530
+ relationships.push({
2531
+ source: "global",
2532
+ target: leaf,
2533
+ type: ASTEngine.RelationshipType.Instantiates,
2534
+ confidence: 0.6,
2535
+ ...this.nodeLine(this.goCalleeNameNode(fn)) ? { line_number: this.nodeLine(this.goCalleeNameNode(fn)) } : {},
2536
+ metadata: { relationshipType: "go_package_scope_instantiation", callExpression: fn.text }
2537
+ });
2538
+ }
2539
+ }
2540
+ }
2541
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2542
+ const child = node.namedChild(i);
2543
+ if (child) stack.push(child);
2544
+ }
2545
+ }
2546
+ }
2547
+ /**
2548
+ * Go-Specific Pattern Analysis
2549
+ * Analyzes goroutines, channels, defer statements, and Go idioms
2550
+ */
2551
+ /**
2552
+ * Go composite-literal construction analysis. `Foo{...}`, `&Foo{...}` and
2553
+ * `pkg.Foo{...}` are Go's primary construction syntax — but before M37
2554
+ * #604 instantiates fired ONLY for NewXxx constructor calls, so struct
2555
+ * construction by literal was invisible to the graph (65,843 composite
2556
+ * literals across go-tools+terraform with zero construction edges). Rust
2557
+ * emits instantiates for struct literals (`Foo { ... }`), csharp for
2558
+ * `new Foo()` — this is family parity, not a new edge kind. Target is the
2559
+ * literal's type LEAF (dotted pkg.Foo -> Foo, matching the calls leaf
2560
+ * contract); source is the containing fn/method or "global" at package
2561
+ * scope. Slice/map/array literals carry a wrapper type child (not a named
2562
+ * type) and elided element literals have no type child — both are skipped
2563
+ * naturally by the type-child gate below.
2564
+ */
2565
+ analyzeGoCompositeLiteralConstruction(rootNode, relationships, symbols = [], stdlibQualifiers = /* @__PURE__ */ new Map()) {
2566
+ const ownPackageName = this.goOwnPackageName(rootNode);
2567
+ const stack = [rootNode];
2568
+ const seen = /* @__PURE__ */ new Set();
2569
+ while (stack.length > 0) {
2570
+ const node = stack.pop();
2571
+ if (node.type === "ERROR" || node.isMissing) continue;
2572
+ if (node.type === "func_literal" && node.parent?.type !== "call_expression" && !this.findContainingGoFunction(node)) {
2573
+ continue;
2574
+ }
2575
+ if (node.type === "composite_literal") {
2576
+ const typeNode = node.childForFieldName("type") ?? (node.namedChildCount > 0 ? node.namedChild(0) : void 0);
2577
+ let target;
2578
+ let qualifiedRaw;
2579
+ if (typeNode) {
2580
+ if (typeNode.type === "type_identifier") {
2581
+ target = typeNode.text;
2582
+ } else if (typeNode.type === "qualified_type") {
2583
+ const raw = typeNode.text;
2584
+ qualifiedRaw = raw;
2585
+ const dot = raw.lastIndexOf(".");
2586
+ target = dot !== -1 ? raw.slice(dot + 1) : raw;
2587
+ } else if (typeNode.type === "generic_type") {
2588
+ const base = typeNode.namedChildCount > 0 ? typeNode.namedChild(0) : void 0;
2589
+ if (base && (base.type === "type_identifier" || base.type === "qualified_type")) {
2590
+ const raw = base.text;
2591
+ if (base.type === "qualified_type") qualifiedRaw = raw;
2592
+ const dot = raw.lastIndexOf(".");
2593
+ target = dot !== -1 ? raw.slice(dot + 1) : raw;
2594
+ }
2595
+ }
2596
+ }
2597
+ if (target && !this.isGoBuiltin(target)) {
2598
+ const src = this.resolveGoWorkSource(node);
2599
+ const key = `${src}.${target}`;
2600
+ if (!seen.has(key)) {
2601
+ seen.add(key);
2602
+ const stdlibModule = qualifiedRaw !== void 0 ? this.goStdlibTypeClaim(qualifiedRaw, target, symbols, stdlibQualifiers, ownPackageName) : null;
2603
+ relationships.push({
2604
+ source: src,
2605
+ target,
2606
+ type: ASTEngine.RelationshipType.Instantiates,
2607
+ confidence: 0.8,
2608
+ // the constructed TYPE's line.
2609
+ ...this.nodeLine(typeNode) ? { line_number: this.nodeLine(typeNode) } : {},
2610
+ metadata: {
2611
+ relationshipType: "go_composite_literal_construction",
2612
+ isConstructor: true,
2613
+ ...qualifiedRaw !== void 0 ? { qualifiedType: qualifiedRaw } : {},
2614
+ ...stdlibModule !== null ? { isStdlib: true, stdlibPath: stdlibModule } : {}
2615
+ }
2616
+ });
2617
+ }
2618
+ }
2619
+ }
2620
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2621
+ const child = node.namedChild(i);
2622
+ if (child) stack.push(child);
2623
+ }
2624
+ }
2625
+ }
2626
+ /**
2627
+ * Go-Specific Pattern Analysis
2628
+ * Analyzes goroutines, channels, defer statements, and Go idioms
2629
+ */
2630
+ analyzeGoSpecificPatterns(rootNode, relationships, symbols) {
2631
+ const dotImported = this.hasGoDotImport(rootNode);
2632
+ const emittedCalls = /* @__PURE__ */ new Set();
2633
+ for (const r of relationships) {
2634
+ if ((r.type || r.relationshipType || "") === ASTEngine.RelationshipType.Calls) {
2635
+ emittedCalls.add(`${r.source}->${r.target}`);
2636
+ }
2637
+ }
2638
+ const emittedChannelUses = /* @__PURE__ */ new Set();
2639
+ const stack = [rootNode];
2640
+ while (stack.length > 0) {
2641
+ const node = stack.pop();
2642
+ if (node.type === "go_statement") {
2643
+ const functionCall = this.extractGoRoutineFunction(node);
2644
+ if (functionCall && this.isGoResolvableCallTarget(functionCall, symbols, dotImported)) {
2645
+ if (this.isGoBuiltin(functionCall.split(".").pop() ?? functionCall)) {
2646
+ continue;
2647
+ }
2648
+ const containingFunction = this.findContainingGoFunction(node);
2649
+ if (containingFunction && !emittedCalls.has(`${containingFunction}->${functionCall}`)) {
2650
+ emittedCalls.add(`${containingFunction}->${functionCall}`);
2651
+ relationships.push({
2652
+ source: containingFunction,
2653
+ target: functionCall,
2654
+ type: ASTEngine.RelationshipType.Calls,
2655
+ confidence: 0.9,
2656
+ // the callee leaf of the `go` statement.
2657
+ ...this.nodeLine(this.goStatementCalleeNode(node)) ? { line_number: this.nodeLine(this.goStatementCalleeNode(node)) } : {},
2658
+ metadata: {
2659
+ relationshipType: "go_goroutine",
2660
+ isAsynchronous: true,
2661
+ pattern: "goroutine_spawn"
2662
+ }
2663
+ });
2664
+ }
2665
+ }
2666
+ }
2667
+ if (node.type === "send_statement" || node.type === "receive_statement" || this.isGoReceiveExpression(node)) {
2668
+ const channelVar = this.extractGoChannelVariable(node);
2669
+ const containingFunction = this.findContainingGoFunction(node) ?? "global";
2670
+ if (channelVar && (channelVar.member || this.isGoDeclaredPackageVar(channelVar.name, symbols))) {
2671
+ const isSend = node.type === "send_statement";
2672
+ const key = `${containingFunction}->${channelVar.name}->${isSend ? "send" : "receive"}`;
2673
+ if (!emittedChannelUses.has(key)) {
2674
+ emittedChannelUses.add(key);
2675
+ relationships.push({
2676
+ source: containingFunction,
2677
+ target: channelVar.name,
2678
+ type: ASTEngine.RelationshipType.Uses,
2679
+ confidence: 0.8,
2680
+ // the send/receive statement's line.
2681
+ ...this.nodeLine(node) ? { line_number: this.nodeLine(node) } : {},
2682
+ metadata: {
2683
+ relationshipType: "go_channel_operation",
2684
+ operationType: isSend ? "send" : "receive",
2685
+ pattern: "channel_communication"
2686
+ }
2687
+ });
2688
+ }
2689
+ }
2690
+ }
2691
+ if (node.type === "defer_statement") {
2692
+ const deferredFunction = this.extractDeferredFunction(node);
2693
+ const containingFunction = this.findContainingGoFunction(node);
2694
+ if (deferredFunction && this.isGoResolvableCallTarget(deferredFunction, symbols, dotImported) && !this.isGoBuiltin(deferredFunction.split(".").pop() ?? deferredFunction) && containingFunction && !emittedCalls.has(`${containingFunction}->${deferredFunction}`)) {
2695
+ emittedCalls.add(`${containingFunction}->${deferredFunction}`);
2696
+ relationships.push({
2697
+ source: containingFunction,
2698
+ target: deferredFunction,
2699
+ type: ASTEngine.RelationshipType.Calls,
2700
+ confidence: 0.9,
2701
+ // the callee leaf of the `defer` statement.
2702
+ ...this.nodeLine(this.goStatementCalleeNode(node)) ? { line_number: this.nodeLine(this.goStatementCalleeNode(node)) } : {},
2703
+ metadata: {
2704
+ relationshipType: "go_defer",
2705
+ isDeferred: true,
2706
+ pattern: "defer_cleanup"
2707
+ }
2708
+ });
2709
+ }
2710
+ }
2711
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2712
+ const child = node.namedChild(i);
2713
+ if (child) stack.push(child);
2714
+ }
2715
+ }
2716
+ }
2717
+ /**
2718
+ * GO1: bare-identifier value reads. Every bare `identifier` that names a
2719
+ * file-visible VALUE (package-scope var/const/func) is a Uses edge, with:
2720
+ * -walked with a lexical scope stack (function/method/func-literal
2721
+ * params+receivers bind at entry; block/short-var/var/range/for/if-init/
2722
+ * select-assign/type-switch-guard bindings bind in walk order, so reads
2723
+ * before any binding still resolve outward — C-family flow discipline);
2724
+ * - declaration positions never read (entity names via first-identifier
2725
+ * rule; composite-literal struct keys skipped; labels/package/import
2726
+ * paths/blank `_` excluded);
2727
+ * - call callees (span-matched; generic/index wrappers climbed),
2728
+ * member leaves (selector path owns them; objects stay readable),
2729
+ * qualified non-leaves, channel operands (channel pass owns them),
2730
+ * assignment/inc/dec LHS writes, go/defer callees all excluded;
2731
+ * - resolvability required: unknown names (std/dot-imported/external)
2732
+ * stay silent — no junk edges.
2733
+ */
2734
+ analyzeGoBareIdentifierUses(rootNode, relationships, symbols) {
2735
+ const fileValues = /* @__PURE__ */ new Set();
2736
+ for (const s of symbols) {
2737
+ if (!s.ownerName && (s.kind === ASTEngine.SymbolKind.Variable || s.kind === ASTEngine.SymbolKind.Constant || s.kind === ASTEngine.SymbolKind.Function)) {
2738
+ fileValues.add(s.name);
2739
+ }
2740
+ }
2741
+ const emitted = /* @__PURE__ */ new Set();
2742
+ const scopeStack = [];
2743
+ const isShadowed = (name) => {
2744
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
2745
+ if (scopeStack[i].names.has(name)) return true;
2746
+ }
2747
+ return false;
2748
+ };
2749
+ const bindNamesUnder = (node, frame) => {
2750
+ const walk = [node];
2751
+ while (walk.length > 0) {
2752
+ const m = walk.pop();
2753
+ if (m !== node && (m.type === "call_expression" || m.type === "composite_literal" || m.type === "func_literal")) {
2754
+ continue;
2755
+ }
2756
+ if (m.type === "identifier" && m.text !== "_") frame.names.add(m.text);
2757
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
2758
+ const c = m.namedChild(i);
2759
+ if (c) walk.push(c);
2760
+ }
2761
+ }
2762
+ };
2763
+ const bindParamsOf = (declNode, frame) => {
2764
+ if (!declNode) return;
2765
+ const seen = [declNode];
2766
+ while (seen.length > 0) {
2767
+ const m = seen.pop();
2768
+ if (m.type === "parameter_declaration" || m.type === "variadic_parameter_declaration") {
2769
+ for (let i = 0; i < m.namedChildCount; i++) {
2770
+ const c = m.namedChild(i);
2771
+ if (!c) continue;
2772
+ if (c.type === "identifier") {
2773
+ if (c.text !== "_") frame.names.add(c.text);
2774
+ } else {
2775
+ break;
2776
+ }
2777
+ }
2778
+ continue;
2779
+ }
2780
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
2781
+ const c = m.namedChild(i);
2782
+ if (c) seen.push(c);
2783
+ }
2784
+ }
2785
+ };
2786
+ const isCompositeKey = (node) => {
2787
+ let cur = node.parent;
2788
+ let keyed = null;
2789
+ while (cur) {
2790
+ if (cur.type === "keyed_element") {
2791
+ const first = cur.namedChildCount > 0 ? cur.namedChild(0) : null;
2792
+ if (first && node.startIndex >= first.startIndex && node.endIndex <= first.endIndex) {
2793
+ keyed = cur;
2794
+ break;
2795
+ }
2796
+ return false;
2797
+ }
2798
+ if (cur.type === "composite_literal" || cur.type === "call_expression" || cur.type === "field_declaration" || cur.type === "function_declaration" || cur.type === "method_declaration" || cur.type === "block") return false;
2799
+ cur = cur.parent;
2800
+ }
2801
+ if (!keyed) return false;
2802
+ cur = keyed.parent;
2803
+ while (cur) {
2804
+ if (cur.type === "composite_literal") {
2805
+ for (let i = 0; i < cur.namedChildCount; i++) {
2806
+ const c = cur.namedChild(i);
2807
+ if (c && c.type === "map_type") return false;
2808
+ }
2809
+ return true;
2810
+ }
2811
+ if (cur.type === "call_expression" || cur.type === "field_declaration" || cur.type === "function_declaration" || cur.type === "method_declaration" || cur.type === "block") return true;
2812
+ cur = cur.parent;
2813
+ }
2814
+ return true;
2815
+ };
2816
+ const isChannelOperand = (node) => {
2817
+ let cur = node.parent;
2818
+ let steps = 0;
2819
+ while (cur && steps < 4) {
2820
+ if (cur.type === "send_statement") {
2821
+ const ch = cur.childForFieldName("channel");
2822
+ return !!ch && node.startIndex >= ch.startIndex && node.endIndex <= ch.endIndex;
2823
+ }
2824
+ if (cur.type === "unary_expression") {
2825
+ const op = cur.child(0);
2826
+ if (op && op.type === "<-") return true;
2827
+ }
2828
+ if (cur.type === "receive_statement" || cur.type === "communication_case") return true;
2829
+ if (cur.type === "call_expression" || cur.type === "field_declaration" || cur.type === "function_declaration" || cur.type === "method_declaration" || cur.type === "block" || cur.type === "expression_list") return false;
2830
+ cur = cur.parent;
2831
+ steps++;
2832
+ }
2833
+ return false;
2834
+ };
2835
+ const isCallCallee = (node) => {
2836
+ let top = node;
2837
+ let p = node.parent;
2838
+ while (p && (p.type === "index_expression" || p.type === "generic_type")) {
2839
+ top = p;
2840
+ p = p.parent;
2841
+ }
2842
+ if (!p || p.type !== "call_expression") return false;
2843
+ const fnField = p.childForFieldName("function");
2844
+ if (fnField) return fnField.startIndex === top.startIndex && fnField.endIndex >= top.endIndex;
2845
+ const first = p.namedChild(0);
2846
+ return !!first && first.startIndex === top.startIndex && first.endIndex >= top.endIndex;
2847
+ };
2848
+ const isAssignLhsWrite = (node) => {
2849
+ let cur = node;
2850
+ let p = node.parent;
2851
+ while (p && p.type === "expression_list") {
2852
+ const gp = p.parent;
2853
+ if (gp && gp.type === "assignment_statement") {
2854
+ const lhs = gp.namedChild(0);
2855
+ return !!lhs && cur.startIndex >= lhs.startIndex && cur.endIndex <= lhs.endIndex;
2856
+ }
2857
+ cur = p;
2858
+ p = p.parent;
2859
+ }
2860
+ return false;
2861
+ };
2862
+ const isEntityName = (node) => {
2863
+ const p = node.parent;
2864
+ if (!p) return false;
2865
+ if (p.type === "function_declaration" || p.type === "method_declaration" || p.type === "type_spec" || p.type === "field_declaration" || p.type === "package_clause" || p.type === "label_name") {
2866
+ for (let i = 0; i < p.namedChildCount; i++) {
2867
+ const c = p.namedChild(i);
2868
+ if (c && (c.type === "identifier" || c.type === "type_identifier" || c.type === "field_identifier")) {
2869
+ return c.startIndex === node.startIndex && c.endIndex === node.endIndex;
2870
+ }
2871
+ }
2872
+ }
2873
+ if (p.type === "var_spec" || p.type === "const_spec") {
2874
+ let seenIdent = false;
2875
+ let isFirstIdent = false;
2876
+ let laterNonIdent = false;
2877
+ let found = false;
2878
+ for (let i = 0; i < p.namedChildCount; i++) {
2879
+ const c = p.namedChild(i);
2880
+ if (!c) continue;
2881
+ if (c.type === "identifier") {
2882
+ if (c.startIndex === node.startIndex && c.endIndex === node.endIndex) {
2883
+ if (!seenIdent) isFirstIdent = true;
2884
+ found = true;
2885
+ }
2886
+ seenIdent = true;
2887
+ } else if (found) {
2888
+ laterNonIdent = true;
2889
+ }
2890
+ }
2891
+ return isFirstIdent || laterNonIdent;
2892
+ }
2893
+ if (p.type === "short_var_declaration" || p.type === "expression_list" && p.parent && p.parent.type === "short_var_declaration") {
2894
+ const decl = p.type === "short_var_declaration" ? p : p.parent;
2895
+ const left = decl.childForFieldName("left") ?? decl.namedChild(0);
2896
+ return !!left && node.startIndex >= left.startIndex && node.endIndex <= left.endIndex;
2897
+ }
2898
+ if (p.type === "goto_statement" || p.type === "break_statement" || p.type === "continue_statement" || p.type === "labeled_statement") return true;
2899
+ return false;
2900
+ };
2901
+ const stack = [{ node: rootNode, exit: false, pushed: false }];
2902
+ while (stack.length > 0) {
2903
+ const { node, exit, pushed } = stack.pop();
2904
+ if (exit) {
2905
+ if (pushed) scopeStack.pop();
2906
+ continue;
2907
+ }
2908
+ const t = node.type;
2909
+ let pushedScope = false;
2910
+ if (t === "function_declaration" || t === "method_declaration" || t === "func_literal") {
2911
+ const frame = { names: /* @__PURE__ */ new Set() };
2912
+ for (let i = 0; i < node.namedChildCount; i++) {
2913
+ const c = node.namedChild(i);
2914
+ if (!c) continue;
2915
+ if (c.type === "parameter_list" || c.type === "type_parameter_list") bindParamsOf(c, frame);
2916
+ }
2917
+ scopeStack.push(frame);
2918
+ pushedScope = true;
2919
+ } else if (t === "block") {
2920
+ scopeStack.push({ names: /* @__PURE__ */ new Set() });
2921
+ pushedScope = true;
2922
+ } else if (t === "var_spec" || t === "const_spec" || t === "short_var_declaration" || t === "range_clause" || t === "type_switch_statement") {
2923
+ const top = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
2924
+ if (top) {
2925
+ if (t === "range_clause") {
2926
+ for (let i = 0; i < node.namedChildCount - 1; i++) {
2927
+ const c = node.namedChild(i);
2928
+ if (c) bindNamesUnder(c, top);
2929
+ }
2930
+ } else if (t === "type_switch_statement") {
2931
+ if (node.namedChildCount > 2) {
2932
+ const first = node.namedChild(0);
2933
+ if (first) bindNamesUnder(first, top);
2934
+ }
2935
+ } else {
2936
+ const walk = [node];
2937
+ while (walk.length > 0) {
2938
+ const m = walk.pop();
2939
+ if (m !== node && (m.type === "call_expression" || m.type === "composite_literal" || m.type === "func_literal")) {
2940
+ continue;
2941
+ }
2942
+ if (m.type === "identifier" && m.text !== "_" && isEntityName(m)) top.names.add(m.text);
2943
+ for (let i = m.namedChildCount - 1; i >= 0; i--) {
2944
+ const c = m.namedChild(i);
2945
+ if (c) walk.push(c);
2946
+ }
2947
+ }
2948
+ }
2949
+ }
2950
+ } else if (t === "receive_statement") {
2951
+ const top = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
2952
+ if (top) {
2953
+ for (let i = 0; i < node.namedChildCount; i++) {
2954
+ const c = node.namedChild(i);
2955
+ if (c && c.type === "expression_list") bindNamesUnder(c, top);
2956
+ }
2957
+ }
2958
+ } else if (t === "identifier") {
2959
+ const name = node.text;
2960
+ const p = node.parent;
2961
+ if (name && name !== "_" && !isEntityName(node) && !isCompositeKey(node) && !isChannelOperand(node) && !isCallCallee(node) && // F6 audit: `field_expression` is not a tree-sitter-go type (that is
2962
+ // rust's name for the shape `r.cfg` here — go uses
2963
+ // `selector_expression`), so the old guard could never fire. It is
2964
+ // also not needed: a selector's receiver is a genuine read
2965
+ // (`x := db.cfg` reads db — probe-verified `f->cfg f->db`), and a
2966
+ // selector that is a call callee is already excluded by
2967
+ // isCallCallee above. Removed rather than re-pointed at
2968
+ // selector_expression, which would silently drop the receiver read.
2969
+ !this.hasGoQualifiedAncestor(node) && // GO9 (second review): assignment LHS is a WRITE — but EVERY
2970
+ // side of every assignment wraps in expression_list, so the old
2971
+ // direct-parent check never fired (see isAssignLhsWrite above).
2972
+ !isAssignLhsWrite(node) && !(p && (p.type === "inc_statement" || p.type === "dec_statement")) && !(p && (p.type === "goto_statement" || p.type === "break_statement" || p.type === "continue_statement" || p.type === "labeled_statement")) && !isShadowed(name) && fileValues.has(name)) {
2973
+ const src = this.resolveGoWorkSource(node);
2974
+ const key = `${src}::uses::${name}`;
2975
+ if (!emitted.has(key)) {
2976
+ emitted.add(key);
2977
+ relationships.push({
2978
+ source: src,
2979
+ target: name,
2980
+ type: ASTEngine.RelationshipType.Uses,
2981
+ confidence: 1,
2982
+ // the identifier's own line.
2983
+ ...this.nodeLine(node) ? { line_number: this.nodeLine(node) } : {},
2984
+ metadata: { relationshipType: "identifier_usage", nodeType: node.type }
2985
+ });
2986
+ }
2987
+ }
2988
+ }
2989
+ stack.push({ node, exit: true, pushed: pushedScope });
2990
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
2991
+ const child = node.namedChild(i);
2992
+ if (child) stack.push({ node: child, exit: false, pushed: false });
2993
+ }
2994
+ }
2995
+ }
2996
+ /**
2997
+ * Go Selector Expression Uses Analysis
2998
+ * Tracks field access on structs (e.g., b.name, s.users).
2999
+ * Family contract (Rust M12.18): source = containing function/method,
3000
+ * target = FIELD LEAF (not the receiver variable). Selectors that are the
3001
+ * callee of a call_expression (`s.repo.Save(...)`, `strings.ToUpper(...)`)
3002
+ * are handled by the calls analysis and skipped here — one edge per pair.
3003
+ */
3004
+ analyzeGoSelectorUses(rootNode, relationships, symbols) {
3005
+ const stack = [rootNode];
3006
+ const seen = /* @__PURE__ */ new Set();
3007
+ while (stack.length > 0) {
3008
+ const node = stack.pop();
3009
+ if (node.type === "selector_expression") {
3010
+ const operand = node.childForFieldName("operand");
3011
+ const field = node.childForFieldName("field");
3012
+ const isWritePos = node.parent?.type === "inc_statement" || node.parent?.type === "dec_statement" || node.parent?.type === "expression_list" && node.parent.parent?.type === "assignment_statement" && node.parent.parent?.namedChild(0)?.id === node.parent.id;
3013
+ const isChannelOperand = node.parent?.type === "send_statement" || node.parent?.type === "communication_case" || node.parent?.type === "unary_expression" && node.parent.child(0)?.type === "<-" || node.parent?.type === "receive_statement" && node.parent.parent?.type === "communication_case";
3014
+ if (operand && field && node.parent?.type !== "call_expression" && !isWritePos && !isChannelOperand) {
3015
+ const src = this.resolveGoWorkSource(node);
3016
+ const tgt = field.text;
3017
+ const key = `${src}.${tgt}`;
3018
+ if (!seen.has(key)) {
3019
+ seen.add(key);
3020
+ relationships.push({
3021
+ source: src,
3022
+ target: tgt,
3023
+ type: ASTEngine.RelationshipType.Uses,
3024
+ confidence: 0.7,
3025
+ // the FIELD identifier's line, not the receiver's —
3026
+ // the c lane's receiver-start lesson applied to `a.b`.
3027
+ ...this.nodeLine(field) ? { line_number: this.nodeLine(field) } : {},
3028
+ metadata: { relationshipType: "go_selector_use", selectorExpression: key }
3029
+ });
3030
+ }
3031
+ }
3032
+ }
3033
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
3034
+ const child = node.namedChild(i);
3035
+ if (child) stack.push(child);
3036
+ }
3037
+ }
3038
+ }
3039
+ /**
3040
+ * Go Import Processing
3041
+ * Handles Go module paths, vendor imports, and package resolution
3042
+ */
3043
+ processGoImports(rootNode, imports) {
3044
+ const stack = [rootNode];
3045
+ while (stack.length > 0) {
3046
+ const node = stack.pop();
3047
+ let claimed = false;
3048
+ if (node.type === "import_spec") {
3049
+ this.processGoImportSpec(node, imports);
3050
+ claimed = true;
3051
+ } else if (node.type === "import_declaration") {
3052
+ this.extractGoImportStatement(node, imports);
3053
+ claimed = true;
3054
+ }
3055
+ if (!claimed) {
3056
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
3057
+ const child = node.namedChild(i);
3058
+ if (child) stack.push(child);
3059
+ }
3060
+ }
3061
+ }
3062
+ }
3063
+ /**
3064
+ * Extract Go import statement with proper module path handling
3065
+ */
3066
+ extractGoImportStatement(node, imports) {
3067
+ const specs = [];
3068
+ const stack = [node];
3069
+ while (stack.length > 0) {
3070
+ const current = stack.pop();
3071
+ if (current.type === "import_spec") {
3072
+ specs.push(current);
3073
+ continue;
3074
+ }
3075
+ for (let i = current.namedChildCount - 1; i >= 0; i--) {
3076
+ const child = current.namedChild(i);
3077
+ if (child) stack.push(child);
3078
+ }
3079
+ }
3080
+ for (const spec of specs) {
3081
+ this.processGoImportSpec(spec, imports);
3082
+ }
3083
+ }
3084
+ /**
3085
+ * Process individual Go import specification
3086
+ */
3087
+ processGoImportSpec(spec, imports) {
3088
+ const pathNode = spec.childForFieldName("path");
3089
+ const aliasNode = spec.childForFieldName("name");
3090
+ if (pathNode) {
3091
+ const importPath = this.stripQuotedString(pathNode.text);
3092
+ const alias = aliasNode?.text;
3093
+ const packageName = this.getGoPackageName(importPath);
3094
+ const framework = this.detectFrameworkFromGoImport(importPath);
3095
+ imports.push({
3096
+ module: importPath,
3097
+ isDefault: false,
3098
+ alias,
3099
+ items: [packageName],
3100
+ range: this.nodeToRange(spec),
3101
+ metadata: {
3102
+ isGoImport: true,
3103
+ packageName,
3104
+ framework,
3105
+ isStandardLibrary: this.isGoStandardLibrary(importPath),
3106
+ isThirdParty: this.isGoThirdPartyPackage(importPath)
3107
+ }
3108
+ });
3109
+ }
3110
+ }
3111
+ /**
3112
+ * Go Framework Pattern Analysis
3113
+ */
3114
+ getGoFrameworkPatterns(frameworks, symbols) {
3115
+ const patterns = [];
3116
+ for (const framework of frameworks) {
3117
+ switch (framework) {
3118
+ case "gin":
3119
+ patterns.push({
3120
+ framework: "Gin Web Framework",
3121
+ type: "web_framework",
3122
+ patterns: this.detectGinPatterns(symbols),
3123
+ confidence: 0.9,
3124
+ characteristics: {
3125
+ hasRoutes: someSymbol(
3126
+ symbols,
3127
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "gin.") || stringIncludes(c, "router."))
3128
+ ),
3129
+ hasMiddleware: someSymbol(
3130
+ symbols,
3131
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Use("))
3132
+ ),
3133
+ hasHandlers: someSymbol(symbols, (s) => stringIncludes(s.name.toLowerCase(), "handler"))
3134
+ }
3135
+ });
3136
+ break;
3137
+ case "echo":
3138
+ patterns.push({
3139
+ framework: "Echo Web Framework",
3140
+ type: "web_framework",
3141
+ patterns: this.detectEchoPatterns(symbols),
3142
+ confidence: 0.9,
3143
+ characteristics: {
3144
+ hasRoutes: someSymbol(
3145
+ symbols,
3146
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "echo.") || stringIncludes(c, "e."))
3147
+ ),
3148
+ hasMiddleware: someSymbol(
3149
+ symbols,
3150
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Use("))
3151
+ ),
3152
+ hasContext: someSymbol(
3153
+ symbols,
3154
+ (s) => someSymbol(s.parameters || [], (p) => stringIncludes(p.type || "", "Context"))
3155
+ )
3156
+ }
3157
+ });
3158
+ break;
3159
+ case "fiber":
3160
+ patterns.push({
3161
+ framework: "Fiber Web Framework",
3162
+ type: "web_framework",
3163
+ patterns: this.detectFiberPatterns(symbols),
3164
+ confidence: 0.9,
3165
+ characteristics: {
3166
+ hasRoutes: someSymbol(
3167
+ symbols,
3168
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "fiber.") || stringIncludes(c, "app."))
3169
+ ),
3170
+ hasFastHTTP: someSymbol(
3171
+ symbols,
3172
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "fasthttp"))
3173
+ )
3174
+ }
3175
+ });
3176
+ break;
3177
+ case "gorilla_mux":
3178
+ patterns.push({
3179
+ framework: "Gorilla Mux Router",
3180
+ type: "router",
3181
+ patterns: this.detectGorillaMuxPatterns(symbols),
3182
+ confidence: 0.8,
3183
+ characteristics: {
3184
+ hasRoutes: someSymbol(
3185
+ symbols,
3186
+ (s) => someSymbol(
3187
+ s.qualified_calls || [],
3188
+ (c) => stringIncludes(c, "mux.") || stringIncludes(c, "HandleFunc")
3189
+ )
3190
+ ),
3191
+ hasSubrouters: someSymbol(
3192
+ symbols,
3193
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "PathPrefix"))
3194
+ )
3195
+ }
3196
+ });
3197
+ break;
3198
+ case "gorm":
3199
+ patterns.push({
3200
+ framework: "GORM ORM",
3201
+ type: "orm",
3202
+ patterns: this.detectGORMPatterns(symbols),
3203
+ confidence: 0.9,
3204
+ characteristics: {
3205
+ hasModels: someSymbol(symbols, (s) => s.kind === ASTEngine.SymbolKind.Struct && s.metadata?.isGoStruct),
3206
+ hasMigrations: someSymbol(
3207
+ symbols,
3208
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "AutoMigrate"))
3209
+ ),
3210
+ hasQueries: someSymbol(
3211
+ symbols,
3212
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Find") || stringIncludes(c, "Create"))
3213
+ )
3214
+ }
3215
+ });
3216
+ break;
3217
+ case "grpc":
3218
+ patterns.push({
3219
+ framework: "gRPC",
3220
+ type: "rpc_framework",
3221
+ patterns: this.detectGRPCPatterns(symbols),
3222
+ confidence: 0.9,
3223
+ characteristics: {
3224
+ hasServices: someSymbol(symbols, (s) => stringIncludes(s.name.toLowerCase(), "service")),
3225
+ hasProtobuf: someSymbol(
3226
+ symbols,
3227
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "proto"))
3228
+ ),
3229
+ hasStreamingMethods: someSymbol(
3230
+ symbols,
3231
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "stream"))
3232
+ )
3233
+ }
3234
+ });
3235
+ break;
3236
+ }
3237
+ }
3238
+ return patterns;
3239
+ }
3240
+ /**
3241
+ * Go Architectural Insights
3242
+ */
3243
+ generateGoArchitecturalInsights(symbols, relationships) {
3244
+ const insights = [];
3245
+ const complexFunctions = filterSymbols(
3246
+ symbols,
3247
+ (s) => (s.kind === ASTEngine.SymbolKind.Function || s.kind === ASTEngine.SymbolKind.Method) && !!s.qualified_calls && s.qualified_calls.length > 12
3248
+ );
3249
+ if (complexFunctions.length > 0) {
3250
+ insights.push({
3251
+ type: "complexity_analysis",
3252
+ category: "function_complexity",
3253
+ message: `${complexFunctions.length} functions with high complexity detected`,
3254
+ functions: mapSymbols(complexFunctions, (f) => f.name),
3255
+ recommendation: "Consider breaking down complex functions into smaller, focused functions",
3256
+ severity: "warning"
3257
+ });
3258
+ }
3259
+ const goroutineFunctions = getUniqueStrings(
3260
+ mapSymbols(
3261
+ filterSymbols(relationships, (r) => r.metadata?.relationshipType === "go_goroutine" && !!r.source),
3262
+ (r) => r.source
3263
+ )
3264
+ );
3265
+ if (goroutineFunctions.length > 0) {
3266
+ insights.push({
3267
+ type: "concurrency_pattern",
3268
+ category: "goroutines",
3269
+ message: `Goroutine usage detected in ${goroutineFunctions.length} functions`,
3270
+ functions: goroutineFunctions,
3271
+ recommendation: "Ensure proper goroutine lifecycle management and channel synchronization"
3272
+ });
3273
+ }
3274
+ const channelFunctions = getUniqueStrings(
3275
+ mapSymbols(
3276
+ filterSymbols(relationships, (r) => r.metadata?.relationshipType === "go_channel_operation" && !!r.source),
3277
+ (r) => r.source
3278
+ )
3279
+ );
3280
+ if (channelFunctions.length > 0) {
3281
+ insights.push({
3282
+ type: "concurrency_pattern",
3283
+ category: "channels",
3284
+ message: `Channel communication pattern detected in ${channelFunctions.length} functions`,
3285
+ functions: channelFunctions,
3286
+ recommendation: "Follow channel ownership patterns and avoid goroutine leaks"
3287
+ });
3288
+ }
3289
+ const structs = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Struct && s.metadata?.isGoStruct);
3290
+ const methods = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Method);
3291
+ if (structs.length > 0) {
3292
+ const methodToStructRatio = methods.length / structs.length;
3293
+ insights.push({
3294
+ type: "design_pattern",
3295
+ category: "struct_design",
3296
+ message: `${structs.length} structs with ${methods.length} methods (ratio: ${methodToStructRatio.toFixed(2)})`,
3297
+ structs: mapSymbols(structs, (s) => s.name),
3298
+ quality: methodToStructRatio > 3 ? "rich_behavior" : methodToStructRatio > 1 ? "balanced" : "anemic_structs",
3299
+ recommendation: methodToStructRatio < 1 ? "Consider adding methods to structs for better encapsulation" : "Good struct-method balance"
3300
+ });
3301
+ }
3302
+ const errorHandling = this.analyzeGoErrorHandling(symbols);
3303
+ if (errorHandling.functions > 0) {
3304
+ insights.push({
3305
+ type: "error_handling",
3306
+ category: "go_errors",
3307
+ message: `Error handling detected in ${errorHandling.functions} functions`,
3308
+ coverage: `${(errorHandling.withErrorHandling / errorHandling.functions * 100).toFixed(1)}%`,
3309
+ recommendation: errorHandling.withErrorHandling / errorHandling.functions < 0.7 ? "Improve error handling coverage" : "Good error handling practices"
3310
+ });
3311
+ }
3312
+ const interfaces = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Interface);
3313
+ if (interfaces.length > 0) {
3314
+ insights.push({
3315
+ type: "design_pattern",
3316
+ category: "interfaces",
3317
+ message: `${interfaces.length} interfaces promoting loose coupling`,
3318
+ interfaces: mapSymbols(interfaces, (i) => i.name),
3319
+ recommendation: "Good use of Go interfaces for abstraction"
3320
+ });
3321
+ }
3322
+ return insights;
3323
+ }
3324
+ // ======================== GO FRAMEWORK DETECTION METHODS ========================
3325
+ detectGinPatterns(symbols) {
3326
+ const patterns = [];
3327
+ if (someSymbol(
3328
+ symbols,
3329
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "gin.Default") || stringIncludes(c, "gin.New"))
3330
+ )) {
3331
+ patterns.push("gin_router_setup");
3332
+ }
3333
+ if (someSymbol(
3334
+ symbols,
3335
+ (s) => someSymbol(
3336
+ s.qualified_calls || [],
3337
+ (c) => stringIncludes(c, "GET") || stringIncludes(c, "POST") || stringIncludes(c, "PUT")
3338
+ )
3339
+ )) {
3340
+ patterns.push("rest_endpoints");
3341
+ }
3342
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Group(")))) {
3343
+ patterns.push("route_grouping");
3344
+ }
3345
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Use(")))) {
3346
+ patterns.push("middleware_usage");
3347
+ }
3348
+ return patterns;
3349
+ }
3350
+ detectEchoPatterns(symbols) {
3351
+ const patterns = [];
3352
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "echo.New")))) {
3353
+ patterns.push("echo_setup");
3354
+ }
3355
+ if (someSymbol(symbols, (s) => someSymbol(s.parameters || [], (p) => stringIncludes(p.type || "", "echo.Context")))) {
3356
+ patterns.push("echo_context_usage");
3357
+ }
3358
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Bind(")))) {
3359
+ patterns.push("request_binding");
3360
+ }
3361
+ return patterns;
3362
+ }
3363
+ detectFiberPatterns(symbols) {
3364
+ const patterns = [];
3365
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "fiber.New")))) {
3366
+ patterns.push("fiber_app_setup");
3367
+ }
3368
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "BodyParser(")))) {
3369
+ patterns.push("request_parsing");
3370
+ }
3371
+ return patterns;
3372
+ }
3373
+ detectGorillaMuxPatterns(symbols) {
3374
+ const patterns = [];
3375
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "mux.NewRouter")))) {
3376
+ patterns.push("gorilla_router_setup");
3377
+ }
3378
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "HandleFunc(")))) {
3379
+ patterns.push("route_handlers");
3380
+ }
3381
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "PathPrefix(")))) {
3382
+ patterns.push("path_prefixes");
3383
+ }
3384
+ return patterns;
3385
+ }
3386
+ detectGORMPatterns(symbols) {
3387
+ const patterns = [];
3388
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "gorm.Open")))) {
3389
+ patterns.push("gorm_connection");
3390
+ }
3391
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "AutoMigrate(")))) {
3392
+ patterns.push("auto_migration");
3393
+ }
3394
+ if (someSymbol(
3395
+ symbols,
3396
+ (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "Create(") || stringIncludes(c, "Find("))
3397
+ )) {
3398
+ patterns.push("crud_operations");
3399
+ }
3400
+ return patterns;
3401
+ }
3402
+ detectGRPCPatterns(symbols) {
3403
+ const patterns = [];
3404
+ if (someSymbol(
3405
+ symbols,
3406
+ (s) => stringIncludes(s.name.toLowerCase(), "service") && s.kind === ASTEngine.SymbolKind.Struct
3407
+ )) {
3408
+ patterns.push("grpc_services");
3409
+ }
3410
+ if (someSymbol(symbols, (s) => someSymbol(s.qualified_calls || [], (c) => stringIncludes(c, "grpc.NewServer")))) {
3411
+ patterns.push("grpc_server_setup");
3412
+ }
3413
+ return patterns;
3414
+ }
3415
+ // ======================== GO HELPER METHODS ========================
3416
+ // GO2: plain-identifier shape check (no regex): letter/underscore head,
3417
+ // word-class tail. Recovery-artifact names (qualified, templated,
3418
+ // operator-soup like `~int | ~string`) fail and emit no symbol/edge.
3419
+ isPlainGoIdentifier(s) {
3420
+ if (s.length === 0) return false;
3421
+ const c0 = s.charCodeAt(0);
3422
+ const firstOk = c0 >= 65 && c0 <= 90 || c0 >= 97 && c0 <= 122 || c0 === 95;
3423
+ if (!firstOk) return false;
3424
+ for (let i = 1; i < s.length; i++) {
3425
+ const c = s.charCodeAt(i);
3426
+ const ok = c >= 65 && c <= 90 || c >= 97 && c <= 122 || c >= 48 && c <= 57 || c === 95;
3427
+ if (!ok) return false;
3428
+ }
3429
+ return true;
3430
+ }
3431
+ // ---- Pure-string helpers (M12.22: no regex in this analyzer) ----
3432
+ /** Strip surrounding backticks/quote chars from a raw string literal. */
3433
+ stripRawStringDelimiters(text) {
3434
+ if (text.length >= 2) {
3435
+ const first = text[0];
3436
+ const last = text[text.length - 1];
3437
+ if (first === "`" && last === "`" || first === "'" && last === "'") {
3438
+ return text.slice(1, -1);
3439
+ }
3440
+ }
3441
+ return text;
3442
+ }
3443
+ /** Strip surrounding double quotes from an interpreted string literal. */
3444
+ stripQuotedString(text) {
3445
+ const q = text.length >= 2 ? text[0] : "";
3446
+ if ((q === '"' || q === "`") && text[text.length - 1] === q) {
3447
+ return text.slice(1, -1);
3448
+ }
3449
+ return text;
3450
+ }
3451
+ /** Parse struct tags `json:"name" db:"x"` into key -> value (no regex). */
3452
+ parseStructTags(tagText) {
3453
+ const result = /* @__PURE__ */ Object.create(null);
3454
+ let i = 0;
3455
+ const n = tagText.length;
3456
+ while (i < n) {
3457
+ while (i < n && (tagText[i] === " " || tagText[i] === " ")) i++;
3458
+ if (i >= n) break;
3459
+ const keyStart = i;
3460
+ while (i < n && tagText[i] !== ":" && tagText[i] !== " " && tagText[i] !== " ") i++;
3461
+ if (i >= n || tagText[i] !== ":") break;
3462
+ const key = tagText.slice(keyStart, i);
3463
+ i++;
3464
+ if (i < n && tagText[i] === '"') {
3465
+ i++;
3466
+ let value = "";
3467
+ while (i < n && tagText[i] !== '"') {
3468
+ if (tagText[i] === "\\" && i + 1 < n) {
3469
+ value += tagText[i + 1];
3470
+ i += 2;
3471
+ } else {
3472
+ value += tagText[i];
3473
+ i++;
3474
+ }
3475
+ }
3476
+ i++;
3477
+ if (key) result[key] = value;
3478
+ }
3479
+ }
3480
+ return result;
3481
+ }
3482
+ /**
3483
+ * Swaggo @Router annotation: `@Router /path/to/endpoint [method]`.
3484
+ * Case-insensitive marker, whitespace-tolerant pure scan (no regex).
3485
+ */
3486
+ parseRouterAnnotation(comment) {
3487
+ const marker = "@Router";
3488
+ const idx = this.indexOfIgnoreCase(comment, marker);
3489
+ if (idx < 0) return void 0;
3490
+ const after = comment.slice(idx + marker.length);
3491
+ const path = this.readToken(after);
3492
+ if (!path) return void 0;
3493
+ const open = after.indexOf("[", after.indexOf(path) + path.length);
3494
+ if (open < 0) return void 0;
3495
+ const close = after.indexOf("]", open);
3496
+ if (close < 0) return void 0;
3497
+ const method = after.slice(open + 1, close).trim();
3498
+ if (!method) return void 0;
3499
+ return `${method.toUpperCase()} ${path}`;
3500
+ }
3501
+ indexOfIgnoreCase(text, search) {
3502
+ const lower = text.toLowerCase();
3503
+ const target = search.toLowerCase();
3504
+ return lower.indexOf(target);
3505
+ }
3506
+ /** Read the next whitespace-delimited token. */
3507
+ readToken(text) {
3508
+ const n = text.length;
3509
+ let i = 0;
3510
+ while (i < n && (text[i] === " " || text[i] === " ")) i++;
3511
+ if (i >= n) return void 0;
3512
+ const start = i;
3513
+ while (i < n && text[i] !== " " && text[i] !== " ") i++;
3514
+ return text.slice(start, i);
3515
+ }
3516
+ /** Normalize a Go signature: collapse whitespace, variadic, interface{} (no regex). */
3517
+ normalizeGoSignature(sig) {
3518
+ let collapsed = "";
3519
+ let prevSpace = false;
3520
+ for (let i = 0; i < sig.length; i++) {
3521
+ const ch = sig[i];
3522
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
3523
+ if (!prevSpace && collapsed.length > 0) collapsed += " ";
3524
+ prevSpace = true;
3525
+ } else {
3526
+ collapsed += ch;
3527
+ prevSpace = false;
3528
+ }
3529
+ }
3530
+ let variadic = "";
3531
+ for (let i = 0; i < collapsed.length; i++) {
3532
+ const ch = collapsed[i];
3533
+ if (ch === "." && collapsed[i + 1] === "." && collapsed[i + 2] === ".") {
3534
+ if (variadic.length > 0 && variadic[variadic.length - 1] === " ") {
3535
+ variadic = variadic.slice(0, -1);
3536
+ }
3537
+ variadic += "...";
3538
+ i += 2;
3539
+ } else {
3540
+ variadic += ch;
3541
+ }
3542
+ }
3543
+ let result = "";
3544
+ let k = 0;
3545
+ while (k < variadic.length) {
3546
+ if (variadic[k] === "i" && variadic.slice(k, k + 11) === "interface{}") {
3547
+ result += "any";
3548
+ k += 11;
3549
+ } else {
3550
+ result += variadic[k];
3551
+ k++;
3552
+ }
3553
+ }
3554
+ let start = 0;
3555
+ let end = result.length;
3556
+ while (start < end && result[start] === " ") start++;
3557
+ while (end > start && result[end - 1] === " ") end--;
3558
+ return result.slice(start, end);
3559
+ }
3560
+ getGoPackageName(importPath) {
3561
+ const parts = importPath.split("/");
3562
+ return parts[parts.length - 1];
3563
+ }
3564
+ detectFrameworkFromGoImport(importPath) {
3565
+ for (const rule of GO_FRAMEWORK_RULES) {
3566
+ if (rule.matches(importPath)) return rule.name;
3567
+ }
3568
+ return void 0;
3569
+ }
3570
+ isGoStandardLibrary(importPath) {
3571
+ const stdLibPrefixes = [
3572
+ "fmt",
3573
+ "net/http",
3574
+ "os",
3575
+ "io",
3576
+ "strings",
3577
+ "strconv",
3578
+ "time",
3579
+ "json",
3580
+ "context",
3581
+ "sync",
3582
+ "log",
3583
+ "bufio",
3584
+ "bytes",
3585
+ "crypto",
3586
+ "database/sql",
3587
+ "encoding",
3588
+ "errors",
3589
+ "flag",
3590
+ "html",
3591
+ "math",
3592
+ "path",
3593
+ "reflect",
3594
+ "regexp",
3595
+ "runtime",
3596
+ "sort",
3597
+ "testing",
3598
+ "unicode",
3599
+ "unsafe"
3600
+ ];
3601
+ return someSymbol(stdLibPrefixes, (prefix) => importPath === prefix || importPath.startsWith(prefix + "/")) || !stringIncludes(importPath, ".");
3602
+ }
3603
+ isGoThirdPartyPackage(importPath) {
3604
+ return stringIncludes(importPath, ".") && !this.isGoStandardLibrary(importPath);
3605
+ }
3606
+ analyzeGoErrorHandling(symbols) {
3607
+ const functions = filterSymbols(
3608
+ symbols,
3609
+ (s) => s.kind === ASTEngine.SymbolKind.Function || s.kind === ASTEngine.SymbolKind.Method
3610
+ );
3611
+ let withErrorHandling = 0;
3612
+ for (const func of functions) {
3613
+ if (func.metadata?.hasErrorHandling) {
3614
+ withErrorHandling++;
3615
+ }
3616
+ }
3617
+ return { functions: functions.length, withErrorHandling };
3618
+ }
3619
+ /**
3620
+ * Go Architectural Score Calculation
3621
+ */
3622
+ calculateArchitecturalScore(symbols, relationships, patterns) {
3623
+ let score = 75;
3624
+ if (inArray(patterns, "error_handling")) score += 8;
3625
+ if (inArray(patterns, "goroutines")) score += 6;
3626
+ if (inArray(patterns, "channels")) score += 6;
3627
+ if (inArray(patterns, "interfaces")) score += 5;
3628
+ if (inArray(patterns, "dependency_injection")) score += 4;
3629
+ const structs = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Struct && s.metadata?.isGoStruct);
3630
+ const methods = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Method);
3631
+ if (structs.length > 0) {
3632
+ const methodToStructRatio = methods.length / structs.length;
3633
+ if (methodToStructRatio >= 1 && methodToStructRatio <= 5) score += 3;
3634
+ }
3635
+ const interfaces = filterSymbols(symbols, (s) => s.kind === ASTEngine.SymbolKind.Interface);
3636
+ if (interfaces.length > 0) {
3637
+ score += Math.min(interfaces.length * 2, 6);
3638
+ }
3639
+ const complexFunctions = filterSymbols(symbols, (s) => !!s.qualified_calls && s.qualified_calls.length > 12);
3640
+ score -= complexFunctions.length * 2;
3641
+ return Math.max(40, Math.min(100, score));
3642
+ }
3643
+ /**
3644
+ * Go Code Quality Assessment
3645
+ */
3646
+ assessCodeQuality(symbols, relationships, patterns) {
3647
+ const score = this.calculateArchitecturalScore(symbols, relationships, patterns);
3648
+ if (score >= 90) return "exceptional";
3649
+ if (score >= 80) return "excellent";
3650
+ if (score >= 70) return "good";
3651
+ if (score >= 60) return "fair";
3652
+ if (score >= 50) return "needs_improvement";
3653
+ return "poor";
3654
+ }
3655
+ /**
3656
+ * Go Cyclomatic Complexity Calculation
3657
+ * Calculates complexity for Go code including goroutines, channels, defer, type switches, and Go-specific patterns
3658
+ */
3659
+ calculateCyclomaticComplexity(node) {
3660
+ let complexity = 1;
3661
+ const traverse = (n) => {
3662
+ switch (n.type) {
3663
+ case "if_statement":
3664
+ case "for_statement":
3665
+ case "expression_case":
3666
+ case "type_case":
3667
+ case "communication_case":
3668
+ case "default_case":
3669
+ complexity++;
3670
+ break;
3671
+ case "binary_expression": {
3672
+ const txt = n.text;
3673
+ if (stringIncludes(txt, "&&") || stringIncludes(txt, "||")) complexity++;
3674
+ break;
3675
+ }
3676
+ }
3677
+ for (let i = 0; i < n.namedChildCount; i++) {
3678
+ const child = n.namedChild(i);
3679
+ if (child) traverse(child);
3680
+ }
3681
+ };
3682
+ traverse(node);
3683
+ return complexity;
3684
+ }
3685
+ // ===== GO HELPER METHODS FOR RELATIONSHIP ANALYSIS =====
3686
+ /**
3687
+ * Find child node by type
3688
+ */
3689
+ findChildByType(node, type) {
3690
+ for (let i = 0; i < node.namedChildCount; i++) {
3691
+ const child = node.namedChild(i);
3692
+ if (child?.type === type) return child;
3693
+ }
3694
+ return null;
3695
+ }
3696
+ findChildrenByType(node, type) {
3697
+ const results = [];
3698
+ for (let i = 0; i < node.namedChildCount; i++) {
3699
+ const child = node.namedChild(i);
3700
+ if (child?.type === type) results.push(child);
3701
+ }
3702
+ return results;
3703
+ }
3704
+ /**
3705
+ * The embedded fields of a struct, as NODES. the `extends`
3706
+ * edge these feed carries the embedded TYPE's own line, so the walk has to
3707
+ * return the node the name is written on and not just its text.
3708
+ *
3709
+ * GO26 (round 5): the `extractGoEmbeddedTypes` TEXT form is removed. It was
3710
+ * defined to delegate here and had no call site anywhere in the repository —
3711
+ * a second entry point onto the same walk, and therefore a second thing that
3712
+ * could drift from it. There is now exactly one.
3713
+ */
3714
+ extractGoEmbeddedTypeNodes(structNode) {
3715
+ const embeddedTypes = [];
3716
+ const fieldList = this.findChildByType(structNode, "field_declaration_list");
3717
+ if (fieldList) {
3718
+ for (let i = 0; i < fieldList.namedChildCount; i++) {
3719
+ const field = fieldList.namedChild(i);
3720
+ if (field?.type === "field_declaration") {
3721
+ const fieldNames = [];
3722
+ for (let j = 0; j < field.namedChildCount; j++) {
3723
+ const c = field.namedChild(j);
3724
+ if (c && (c.type === "field_identifier" || c.type === "identifier")) fieldNames.push(c);
3725
+ }
3726
+ if (fieldNames.length === 0) {
3727
+ const typeNode = field.childForFieldName("type") || this.findChildByType(field, "type_identifier");
3728
+ if (typeNode) {
3729
+ embeddedTypes.push(typeNode);
3730
+ }
3731
+ }
3732
+ }
3733
+ }
3734
+ }
3735
+ return embeddedTypes;
3736
+ }
3737
+ /**
3738
+ * Get interface methods from interface symbol
3739
+ */
3740
+ getGoInterfaceMethods(interfaceSymbol, symbols, visiting) {
3741
+ const seen = visiting ?? /* @__PURE__ */ new Set();
3742
+ if (seen.has(interfaceSymbol.name)) return [];
3743
+ seen.add(interfaceSymbol.name);
3744
+ const interfaceMethods = [];
3745
+ const methods = filterSymbols(
3746
+ symbols,
3747
+ (s) => s.kind === ASTEngine.SymbolKind.Method && s.ownerName === interfaceSymbol.name
3748
+ );
3749
+ forEachSymbol(methods, (method) => {
3750
+ const signature = this.buildGoMethodSignature(method);
3751
+ interfaceMethods.push({
3752
+ name: method.name,
3753
+ signature
3754
+ });
3755
+ });
3756
+ const embedded = filterSymbols(
3757
+ symbols,
3758
+ (s) => s.ownerName === interfaceSymbol.name && s.ownerKind === ASTEngine.SymbolKind.Interface && s.metadata?.isEmbeddedInterface
3759
+ );
3760
+ forEachSymbol(embedded, (entry) => {
3761
+ const target = findSymbol(
3762
+ symbols,
3763
+ (s) => s.kind === ASTEngine.SymbolKind.Interface && s.name === entry.name && !s.metadata?.isEmbeddedInterface
3764
+ );
3765
+ if (!target) return;
3766
+ const inherited = this.getGoInterfaceMethods(target, symbols, seen);
3767
+ forEachSymbol(inherited, (m) => {
3768
+ if (!someSymbol(interfaceMethods, (x) => x.name === m.name && x.signature === m.signature)) {
3769
+ interfaceMethods.push(m);
3770
+ }
3771
+ });
3772
+ });
3773
+ seen.delete(interfaceSymbol.name);
3774
+ return interfaceMethods;
3775
+ }
3776
+ /**
3777
+ * Check if struct implements interface (Go's implicit interface implementation)
3778
+ */
3779
+ checkGoInterfaceImplementation(interfaceMethods, structMethods) {
3780
+ const matchedMethods = [];
3781
+ const totalMethods = interfaceMethods.length;
3782
+ if (totalMethods === 0) {
3783
+ return { implements: false, confidence: 0, matchedMethods: [] };
3784
+ }
3785
+ const available = /* @__PURE__ */ new Set();
3786
+ forEachSymbol(structMethods, (m) => {
3787
+ available.add(`${m.name}\0${this.normalizeGoSignature(this.buildGoMethodSignature(m))}`);
3788
+ });
3789
+ forEachSymbol(interfaceMethods, (interfaceMethod) => {
3790
+ if (available.has(`${interfaceMethod.name}\0${this.normalizeGoSignature(interfaceMethod.signature)}`)) {
3791
+ matchedMethods.push(interfaceMethod.name);
3792
+ }
3793
+ });
3794
+ const doesImplement = matchedMethods.length === totalMethods;
3795
+ const confidence = doesImplement ? 1 : matchedMethods.length / totalMethods;
3796
+ return { implements: doesImplement, confidence, matchedMethods };
3797
+ }
3798
+ /**
3799
+ * Build Go method signature for comparison
3800
+ */
3801
+ buildGoMethodSignature(method) {
3802
+ const key = method.range?.startIndex;
3803
+ if (typeof key === "number") {
3804
+ const hit = this.goSignatures.get(key);
3805
+ if (hit !== void 0) return hit;
3806
+ }
3807
+ const params = mapSymbols(method.parameters || [], (p) => (p.isVariadic ? "..." : "") + (p.type || "interface{}")).join(", ") || "";
3808
+ const returnType = method.returnType || "void";
3809
+ const sig = `${method.name}(${params}) ${returnType}`;
3810
+ if (typeof key === "number") this.goSignatures.set(key, sig);
3811
+ return sig;
3812
+ }
3813
+ /**
3814
+ * The name of THIS file's own package, read from the grammar.
3815
+ *
3816
+ * `package_clause` has NO fields in tree-sitter-go 0.25.0's node-types.json:
3817
+ * the identifier is a plain named child. Reading `childForFieldName("name")`
3818
+ * here returns "" and the caller would then reject EVERY selector callee,
3819
+ * silently deleting the self-qualified constructions the edge is supposed to
3820
+ * carry — the same class of silent total loss the oracle hit on its first run.
3821
+ */
3822
+ goOwnPackageName(rootNode) {
3823
+ const pkgClause = this.findChildByType(rootNode, "package_clause");
3824
+ if (!pkgClause) return "";
3825
+ for (let i = 0; i < pkgClause.namedChildCount; i++) {
3826
+ const c = pkgClause.namedChild(i);
3827
+ if (c && c.type === "package_identifier") return String(c.text ?? "");
3828
+ }
3829
+ return "";
3830
+ }
3831
+ /**
3832
+ * Can this call's callee be the top-level function THIS FILE declares?
3833
+ *
3834
+ * The `instantiates` edge's target is always a symbol from this file's own
3835
+ * symbol table, so the question is not "is this a package-qualified call" —
3836
+ * it is "does this call select something this file could be responsible for".
3837
+ * Two shapes qualify:
3838
+ *
3839
+ * `NewReader(1)` a bare identifier call. In Go a bare call at
3840
+ * package scope can only reach a name in this
3841
+ * package (or a dot-imported one, which the
3842
+ * leniency parameter already handles).
3843
+ * `calleeshape.NewReader(3)` the file's OWN package name as the qualifier.
3844
+ * Legal Go — `src/html/template/escape.go:977` is
3845
+ * `func HTMLEscape(...) { template.HTMLEscape(w, b) }`
3846
+ * — and it selects this file's top-level function.
3847
+ *
3848
+ * Any OTHER qualifier does not qualify, and the reason is the bug this method
3849
+ * exists for. `bufio.NewReader(r)` is a top-level function of `bufio`, but the
3850
+ * `instantiates` target had to come from THIS file's symbol table, so a file
3851
+ * that happened to declare its own `NewReader` published
3852
+ * `instantiates ReceiverCollision -> NewReader` for a construction it did not
3853
+ * perform. The callee's shape was right; the callee's PACKAGE was not the one
3854
+ * the target symbol lives in. Counting import aliases as qualifiers does not
3855
+ * fix this — an alias can collide with a same-named local function just as
3856
+ * easily as a package name can.
3857
+ *
3858
+ * Measured over a 500-file stride sample of go-stdlib: 155 `instantiates`
3859
+ * edges at confidence 0.6, 8 of them (6 files) naming a symbol in the wrong
3860
+ * package. See validation/m14/go/go-mislabel-probe.ts and the fixture
3861
+ * `instantiates-callee-shape`.
3862
+ */
3863
+ isGoTopLevelFunctionCallee(calleeNode, ownPackageName) {
3864
+ if (!calleeNode) return false;
3865
+ if (calleeNode.type === "identifier") return true;
3866
+ if (calleeNode.type !== "selector_expression") return false;
3867
+ const operand = calleeNode.childForFieldName("operand");
3868
+ if (!operand || operand.type !== "identifier") return false;
3869
+ if (ownPackageName.length === 0) return false;
3870
+ return String(operand.text ?? "") === ownPackageName;
3871
+ }
3872
+ /**
3873
+ * Resolve Go function call to find target
3874
+ */
3875
+ resolveGoFunctionCall(callExpression, symbols, dotImported = false) {
3876
+ if (stringIncludes(callExpression, ".")) {
3877
+ const parts = callExpression.split(".");
3878
+ const functionName = parts[parts.length - 1];
3879
+ if (!functionName) return void 0;
3880
+ const fallback = findSymbol(symbols, (s) => this.isGoPackageCallable(s) && s.name === functionName);
3881
+ return fallback?.name ?? functionName;
3882
+ }
3883
+ if (dotImported) return callExpression || void 0;
3884
+ const targetFunction = findSymbol(symbols, (s) => this.isGoPackageCallable(s) && s.name === callExpression);
3885
+ return targetFunction?.name;
3886
+ }
3887
+ /**
3888
+ * Extract Go package name from content
3889
+ */
3890
+ findGoPackageClause(rootNode) {
3891
+ return this.findChildByType(rootNode, "package_clause") ?? void 0;
3892
+ }
3893
+ /**
3894
+ * The package name a `package_clause` declares. M14.0.F1 (#880):
3895
+ * `package_clause` declares NO fields in this grammar, so the old
3896
+ * `childForFieldName("package_identifier")` could never match (that is a
3897
+ * node TYPE, not a field) and the named-child fallback carried the value all
3898
+ * along. Read the node by TYPE, so the audit's field table and this lookup
3899
+ * agree.
3900
+ */
3901
+ goPackageClauseName(clause) {
3902
+ const pkgId = this.findChildByType(clause, "package_identifier") ?? clause.namedChild(0);
3903
+ return pkgId ? pkgId.text : void 0;
3904
+ }
3905
+ /**
3906
+ * Extract function called in goroutine
3907
+ */
3908
+ extractGoRoutineFunction(node) {
3909
+ const callExpr = this.findChildByType(node, "call_expression");
3910
+ if (callExpr) {
3911
+ const functionNode = callExpr.childForFieldName("function");
3912
+ return this.resolveGoCallLeaf(functionNode);
3913
+ }
3914
+ return void 0;
3915
+ }
3916
+ /**
3917
+ * Find containing Go function for a node
3918
+ */
3919
+ findContainingGoFunction(node) {
3920
+ let current = node.parent;
3921
+ while (current) {
3922
+ if (current.type === "function_declaration" || current.type === "method_declaration") {
3923
+ const nameNode = current.childForFieldName("name");
3924
+ return nameNode?.text;
3925
+ }
3926
+ current = current.parent;
3927
+ }
3928
+ return void 0;
3929
+ }
3930
+ /**
3931
+ * GOD12 (round 7): the SOURCE for work done inside a PACKAGE-SCOPE initializer.
3932
+ *
3933
+ * `findContainingGoFunction` walks up for a `function_declaration` or
3934
+ * `method_declaration` and returns undefined when there is none, and every
3935
+ * caller turned that into the `global` pseudo-source. That is correct for a
3936
+ * statement at file scope and wrong for the very common
3937
+ *
3938
+ * var re = regexp.MustCompile(`...`)
3939
+ * var api = http.Client{Timeout: time.Second}
3940
+ * var host = os.Getenv("HOST") + ".example.com"
3941
+ *
3942
+ * where the call, the construction and the selector read are all work the
3943
+ * DECLARATION is doing, for a binding the analyzer has already published as a
3944
+ * symbol. At full extent that was 6,723 edges whose source named nothing:
3945
+ * 3,396 `uses`, 1,391 `calls`, 985 `instantiates`, 947 `references`.
3946
+ *
3947
+ * The rule is the one the declared-type pass already follows: a declaration
3948
+ * attributes to what it declares. `global` remains the answer for work that is
3949
+ * genuinely at file scope, and a FUNCTION-LOCAL `var` deliberately does not
3950
+ * use its name — LocalsAreNotSymbols makes a local var not a symbol, so
3951
+ * naming it would trade one unresolvable source for another. A `func_literal`
3952
+ * is skipped rather than treated as a stop, which is the pre-existing
3953
+ * behaviour and is also what makes `var y = func() { f() }()` attribute to `y`.
3954
+ */
3955
+ resolveGoWorkSource(node) {
3956
+ const declaredNameOf = (spec) => {
3957
+ for (let q = spec.parent; q; q = q.parent) {
3958
+ if (q.type === "function_declaration" || q.type === "method_declaration" || q.type === "func_literal") return void 0;
3959
+ }
3960
+ for (let i = 0; i < spec.namedChildCount; i++) {
3961
+ const c = spec.namedChild(i);
3962
+ if (c && c.type === "identifier" && String(c.text ?? "") !== "_") return String(c.text ?? "");
3963
+ }
3964
+ return void 0;
3965
+ };
3966
+ for (let q = node; q; q = q.parent) {
3967
+ if (q.type === "function_declaration" || q.type === "method_declaration") {
3968
+ for (let i = 0; i < q.namedChildCount; i++) {
3969
+ const c = q.namedChild(i);
3970
+ if (!c) continue;
3971
+ if (q.type === "method_declaration" && c.type === "field_identifier" || q.type === "function_declaration" && c.type === "identifier") return String(c.text ?? "");
3972
+ }
3973
+ return "global";
3974
+ }
3975
+ if (q.type === "var_spec" || q.type === "const_spec") {
3976
+ const nm = declaredNameOf(q);
3977
+ if (nm) return nm;
3978
+ }
3979
+ }
3980
+ return "global";
3981
+ }
3982
+ /**
3983
+ * Extract channel variable from channel operation
3984
+ */
3985
+ // GOD8 (M37 #604 wave-3): channel-op target resolution. Returns the
3986
+ // channel's declared-name LEAF for (a) a bare identifier naming a
3987
+ // PACKAGE-scope var/const (caller gates via isGoDeclaredPackageVar) and
3988
+ // (b) a member selector (leaf = field name — the channel-op pass owns
3989
+ // member channel edges with send/receive direction, so the selector pass
3990
+ // skips channel operands). Call results (`<-ctx.Done()`) and bare
3991
+ // LOCAL/param channels (`c <- v`) have no declared channel symbol and
3992
+ // return undefined (LocalsAreNotSymbols — same resolvability gate the
3993
+ // goroutine/defer branches apply via isGoResolvableCallTarget).
3994
+ extractGoChannelVariable(node) {
3995
+ const operandNode = (n) => {
3996
+ if (n.type === "send_statement") {
3997
+ const c = n.childForFieldName("channel");
3998
+ return c?.type === "identifier" || c?.type === "selector_expression" ? c : void 0;
3999
+ }
4000
+ const direct = n.childForFieldName("operand") || (n.namedChildCount > 0 ? n.namedChild(0) : void 0);
4001
+ const unwrapped = direct && (direct.type === "unary_expression" || direct.type === "receive_statement") ? direct.namedChildCount > 0 ? direct.namedChild(0) : void 0 : direct;
4002
+ return unwrapped?.type === "identifier" || unwrapped?.type === "selector_expression" ? unwrapped : void 0;
4003
+ };
4004
+ if (node.type === "send_statement" || node.type === "receive_statement" || this.isGoReceiveExpression(node)) {
4005
+ const op = operandNode(node);
4006
+ if (!op) return void 0;
4007
+ if (op.type === "identifier") return { name: op.text, member: false };
4008
+ if (op.type === "selector_expression") {
4009
+ const f = op.childForFieldName("field");
4010
+ return f ? { name: f.text, member: true } : void 0;
4011
+ }
4012
+ }
4013
+ return void 0;
4014
+ }
4015
+ isGoReceiveExpression(node) {
4016
+ return node.type === "unary_expression" && node.child(0)?.type === "<-";
4017
+ }
4018
+ // GOD8: a bare identifier is a valid channel-op target only when it names a
4019
+ // declared PACKAGE-scope var/const (LocalsAreNotSymbols — locals/params
4020
+ // never qualify; the same resolvability gate the goroutine/defer branches
4021
+ // apply via isGoResolvableCallTarget). Member selectors (member=true) are
4022
+ // always valid — the field is a declared member.
4023
+ isGoDeclaredPackageVar(name, symbols) {
4024
+ return someSymbol(
4025
+ symbols,
4026
+ (s) => s.name === name && (s.kind === ASTEngine.SymbolKind.Variable || s.kind === ASTEngine.SymbolKind.Constant) && !s.ownerName
4027
+ );
4028
+ }
4029
+ /**
4030
+ * Extract deferred function from defer statement
4031
+ */
4032
+ extractDeferredFunction(node) {
4033
+ const callExpr = this.findChildByType(node, "call_expression");
4034
+ if (callExpr) {
4035
+ const functionNode = callExpr.childForFieldName("function");
4036
+ return this.resolveGoCallLeaf(functionNode);
4037
+ }
4038
+ return void 0;
4039
+ }
4040
+ extractGoMethodSpecParameters(node) {
4041
+ const parameters = [];
4042
+ const paramList = this.findChildByType(node, "parameter_list");
4043
+ if (!paramList) return parameters;
4044
+ for (let i = 0; i < paramList.namedChildCount; i++) {
4045
+ const param = paramList.namedChild(i);
4046
+ const isVariadic = param?.type === "variadic_parameter_declaration";
4047
+ if (param?.type !== "parameter_declaration" && !isVariadic) continue;
4048
+ const typeNode = param.childForFieldName("type");
4049
+ const nameNodes = [];
4050
+ for (let j = 0; j < param.namedChildCount; j++) {
4051
+ const c = param.namedChild(j);
4052
+ if (c && c.type === "identifier") nameNodes.push(c);
4053
+ }
4054
+ if (nameNodes.length > 0) {
4055
+ for (const nameNode of nameNodes) {
4056
+ if (!nameNode) continue;
4057
+ parameters.push({
4058
+ name: nameNode.text,
4059
+ type: typeNode?.text,
4060
+ isOptional: false,
4061
+ ...isVariadic ? { isVariadic: true } : {}
4062
+ });
4063
+ }
4064
+ } else if (typeNode) {
4065
+ parameters.push({
4066
+ name: `arg${parameters.length}`,
4067
+ type: typeNode.text,
4068
+ isOptional: false,
4069
+ ...isVariadic ? { isVariadic: true } : {}
4070
+ });
4071
+ }
4072
+ }
4073
+ return parameters;
4074
+ }
4075
+ extractGoMethodSpecReturnType(node) {
4076
+ const resultNode = node.childForFieldName("result");
4077
+ if (resultNode) {
4078
+ if (resultNode.type === "parameter_list") {
4079
+ const types = [];
4080
+ for (let i = 0; i < resultNode.namedChildCount; i++) {
4081
+ const p = resultNode.namedChild(i);
4082
+ if (!p || p.type !== "parameter_declaration") continue;
4083
+ const t = p.childForFieldName("type");
4084
+ if (!t) continue;
4085
+ let names = 0;
4086
+ for (let j = 0; j < p.namedChildCount; j++) {
4087
+ const c = p.namedChild(j);
4088
+ if (c && c.type === "identifier") names++;
4089
+ }
4090
+ for (let k = 0; k < Math.max(1, names); k++) types.push(t.text);
4091
+ }
4092
+ if (types.length === 1) return types[0];
4093
+ if (types.length > 1) return `(${types.join(", ")})`;
4094
+ return resultNode.text;
4095
+ }
4096
+ return resultNode.text;
4097
+ }
4098
+ for (let i = 0; i < node.namedChildCount; i++) {
4099
+ const child = node.namedChild(i);
4100
+ if (child && inArray(
4101
+ [
4102
+ "type_identifier",
4103
+ "qualified_type",
4104
+ "pointer_type",
4105
+ "slice_type",
4106
+ "array_type",
4107
+ "map_type",
4108
+ "channel_type",
4109
+ "function_type"
4110
+ ],
4111
+ child.type
4112
+ )) {
4113
+ return child.text;
4114
+ }
4115
+ }
4116
+ return void 0;
4117
+ }
4118
+ hasGoErrorHandling(text) {
4119
+ if (!text) return false;
4120
+ return stringIncludes(text, "err != nil") || stringIncludes(text, "fmt.Errorf(") || stringIncludes(text, "errors.New(") || stringIncludes(text, "panic(");
4121
+ }
4122
+ };
4123
+ export {
4124
+ GO_STDLIB_TOP_LEVEL,
4125
+ RealGoAnalyzer
4126
+ };