@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,4301 @@
1
+ import {
2
+ USAGE_COUNT_UNIT_LABEL,
3
+ USAGE_SITE_FILTER_LABEL,
4
+ countUnresolvedNameMatches,
5
+ isDeclarationRelationship,
6
+ isUsageSiteRow,
7
+ usageSites
8
+ } from "../chunk-GBI7V7KY.js";
9
+ import {
10
+ TypeInference
11
+ } from "../chunk-7DRPNVIN.js";
12
+ import {
13
+ UNRESOLVED_TYPE,
14
+ crossLanguageSection,
15
+ factsFromRows,
16
+ parseParameters,
17
+ renderSignature,
18
+ storedCrossLanguageRows
19
+ } from "../chunk-RM3PY6TG.js";
20
+ import {
21
+ classifyStoredRow,
22
+ orderTiers,
23
+ primaryTier
24
+ } from "../chunk-RCMTFSEF.js";
25
+ import {
26
+ ADDRESS_NOTES,
27
+ STORED_LINE_SELECT,
28
+ addressLine0,
29
+ deriveCallSiteAddress,
30
+ isExactAddress,
31
+ storedLineFromRow,
32
+ traverseGraph
33
+ } from "../chunk-ENW4BTBA.js";
34
+ import {
35
+ isCommentNodeType,
36
+ isIdentifierNodeType,
37
+ parseForSafety
38
+ } from "../chunk-5BVAH26D.js";
39
+ import {
40
+ getProjectId
41
+ } from "../chunk-DIYAGQJL.js";
42
+ import "../chunk-UUNHHX4M.js";
43
+ import {
44
+ BACKBENCHER_BRAIN_VERSION
45
+ } from "../chunk-WIG36E67.js";
46
+ import {
47
+ checkFileFreshness,
48
+ hashFileContent
49
+ } from "../chunk-QLK6ED4Q.js";
50
+ import {
51
+ BrainDatabase
52
+ } from "../chunk-QKTRECYS.js";
53
+ import {
54
+ ASTEngine
55
+ } from "../chunk-3ZTOHHSW.js";
56
+ import "../chunk-BQ3OVQ3P.js";
57
+ import "../chunk-SK6YVHFZ.js";
58
+ import "../chunk-2BSKD7O3.js";
59
+ import "../chunk-KAET47ZL.js";
60
+ import "../chunk-KCDECRJY.js";
61
+ import "../chunk-SM7RFQRO.js";
62
+ import "../chunk-L5TDYKYK.js";
63
+ import "../chunk-ROMM6KMC.js";
64
+ import "../chunk-MLKGABMK.js";
65
+
66
+ // src/lsp/server.ts
67
+ import path2 from "path";
68
+ import { realpathSync, appendFileSync } from "fs";
69
+
70
+ // src/lsp/document_highlight.ts
71
+ import { readFileSync as readFileSync5 } from "fs";
72
+
73
+ // src/lsp/hover.ts
74
+ import { readFileSync as readFileSync4 } from "fs";
75
+
76
+ // src/lsp/positions.ts
77
+ var PositionError = class extends Error {
78
+ constructor(message) {
79
+ super(message);
80
+ this.name = "PositionError";
81
+ }
82
+ };
83
+ function resolveRowByteColumns(sourceText, line, byte, linesIn) {
84
+ if (!Number.isInteger(line) || line < 1 || !Number.isInteger(byte) || byte < 0) return null;
85
+ const lines = linesIn ?? sourceText.split("\n");
86
+ if (line > lines.length) return null;
87
+ if (byte === 0) return line === 1 ? { line: 1, column: 1 } : null;
88
+ const line0 = line - 1;
89
+ const lineByteLen = Buffer.byteLength(lines[line0], "utf8");
90
+ if (byte <= lineByteLen + 1) return { line, column: byte };
91
+ let cumStart = 0;
92
+ for (let i = 0; i < lines.length; i++) {
93
+ const len = Buffer.byteLength(lines[i], "utf8") + 1;
94
+ if (byte >= cumStart && byte < cumStart + len) {
95
+ const col = byte - cumStart + 1;
96
+ if (col <= Buffer.byteLength(lines[i], "utf8") + 1) return { line: i + 1, column: col };
97
+ return null;
98
+ }
99
+ cumStart += len;
100
+ }
101
+ return null;
102
+ }
103
+ var PositionMapper = class {
104
+ /** 0-based line -> UTF-16 code unit length of that line (excluding terminator) */
105
+ lineUtf16Lengths;
106
+ /** 0-based line -> byte length of that line (excluding terminator) */
107
+ lineByteLengths;
108
+ /** 0-based line -> the line's text (excluding terminator) */
109
+ lineTexts;
110
+ constructor(sourceText) {
111
+ this.lineTexts = sourceText.split(/\r\n|\r|\n/);
112
+ this.lineUtf16Lengths = this.lineTexts.map((line) => line.length);
113
+ this.lineByteLengths = this.lineTexts.map((line) => Buffer.byteLength(line, "utf8"));
114
+ }
115
+ assertLine(line0) {
116
+ if (!Number.isInteger(line0) || line0 < 0 || line0 >= this.lineTexts.length) {
117
+ throw new PositionError(`line ${line0} out of range (0..${this.lineTexts.length - 1})`);
118
+ }
119
+ }
120
+ /**
121
+ * Convert a byte offset within a line to a UTF-16 code unit offset.
122
+ * The byte offset must land on a code point boundary.
123
+ */
124
+ byteOffsetToUtf16Offset(line0, byteOffset) {
125
+ this.assertLine(line0);
126
+ const text = this.lineTexts[line0];
127
+ const byteLen = this.lineByteLengths[line0];
128
+ if (!Number.isInteger(byteOffset) || byteOffset < 0 || byteOffset > byteLen) {
129
+ throw new PositionError(`byte offset ${byteOffset} out of range (0..${byteLen}) on line ${line0 + 1}`);
130
+ }
131
+ if (byteOffset === 0) return 0;
132
+ if (byteOffset === byteLen) return text.length;
133
+ const buf = Buffer.from(text, "utf8");
134
+ if ((buf[byteOffset] & 192) === 128) {
135
+ throw new PositionError(`byte offset ${byteOffset} on line ${line0 + 1} lands mid-code-point`);
136
+ }
137
+ return buf.subarray(0, byteOffset).toString("utf8").length;
138
+ }
139
+ /**
140
+ * Convert a UTF-16 code unit offset within a line to a byte offset.
141
+ * A low-surrogate offset is not a character start — fail rather than guess.
142
+ */
143
+ utf16OffsetToByteOffset(line0, utf16Offset) {
144
+ this.assertLine(line0);
145
+ const text = this.lineTexts[line0];
146
+ const utf16Len = this.lineUtf16Lengths[line0];
147
+ if (!Number.isInteger(utf16Offset) || utf16Offset < 0 || utf16Offset > utf16Len) {
148
+ throw new PositionError(`utf16 offset ${utf16Offset} out of range (0..${utf16Len}) on line ${line0 + 1}`);
149
+ }
150
+ if (utf16Offset === 0) return 0;
151
+ if (utf16Offset === utf16Len) return this.lineByteLengths[line0];
152
+ const unit = text.charCodeAt(utf16Offset);
153
+ if (unit >= 56320 && unit <= 57343) {
154
+ throw new PositionError(`utf16 offset ${utf16Offset} on line ${line0 + 1} lands on a surrogate trailing half`);
155
+ }
156
+ return Buffer.byteLength(text.slice(0, utf16Offset), "utf8");
157
+ }
158
+ indexToLsp(pos) {
159
+ if (!Number.isInteger(pos.line) || pos.line < 1) {
160
+ throw new PositionError(`index line must be >= 1, got ${pos.line}`);
161
+ }
162
+ if (!Number.isInteger(pos.column) || pos.column < 1) {
163
+ throw new PositionError(`index column must be >= 1, got ${pos.column}`);
164
+ }
165
+ const line0 = pos.line - 1;
166
+ const character = this.byteOffsetToUtf16Offset(line0, pos.column - 1);
167
+ return { line: line0, character };
168
+ }
169
+ lspToIndex(pos) {
170
+ if (!Number.isInteger(pos.line) || pos.line < 0) {
171
+ throw new PositionError(`LSP line must be >= 0, got ${pos.line}`);
172
+ }
173
+ if (!Number.isInteger(pos.character) || pos.character < 0) {
174
+ throw new PositionError(`LSP character must be >= 0, got ${pos.character}`);
175
+ }
176
+ const line0 = pos.line;
177
+ const byteOffset = this.utf16OffsetToByteOffset(line0, pos.character);
178
+ return { line: line0 + 1, column: byteOffset + 1 };
179
+ }
180
+ indexRangeToLsp(range) {
181
+ return {
182
+ start: this.indexToLsp(range.start),
183
+ end: this.indexToLsp(range.end)
184
+ };
185
+ }
186
+ lspRangeToIndex(range) {
187
+ return {
188
+ start: this.lspToIndex(range.start),
189
+ end: this.lspToIndex(range.end)
190
+ };
191
+ }
192
+ };
193
+ function createPositionMapper(sourceText) {
194
+ const mapper = new PositionMapper(sourceText);
195
+ return Object.assign(mapper, { get lineCount() {
196
+ return mapper.lineTexts.length;
197
+ } });
198
+ }
199
+
200
+ // src/lsp/annotation_ui.ts
201
+ function escapeAnnotationText(text) {
202
+ return text.replace(/[\r\n\u2028\u2029]+/g, " ").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
203
+ }
204
+ function formatAnnotationTime(epochMs) {
205
+ return new Date(epochMs).toISOString().slice(0, 10);
206
+ }
207
+ function annotationHoverSection(rows) {
208
+ if (!Array.isArray(rows) || rows.length === 0) return "";
209
+ const lines = ["**notes** (agent/human annotations \u2014 provenance recorded, not index-derived):"];
210
+ for (const row of rows) {
211
+ const who = `${escapeAnnotationText(row.author_class)} note by ${escapeAnnotationText(row.tool_identity)}`;
212
+ const when = formatAnnotationTime(row.time_updated);
213
+ const scope = row.symbol_path === null || row.symbol_path === void 0 ? "name-level" : "this file";
214
+ lines.push(`- [${who}, ${when}, ${scope}]: ${escapeAnnotationText(row.body)}`);
215
+ }
216
+ return lines.join("\n");
217
+ }
218
+
219
+ // src/lsp/doc_comment.ts
220
+ function stripCommentMarkup(raw) {
221
+ const lines = raw.split("\n");
222
+ const out = [];
223
+ for (let i = 0; i < lines.length; i++) {
224
+ let l = lines[i].replace(/\r$/, "");
225
+ if (i === 0) l = l.replace(/^\s*\/\*+\s?/, "").replace(/^\s*#+\s?/, "").replace(/^\s*--+\s?/, "");
226
+ if (i === lines.length - 1) l = l.replace(/\s?\*+\/\s*$/, "");
227
+ l = l.replace(/^\s*\*\s?/, "");
228
+ l = l.replace(/^\s*\/\/\/\s?/, "");
229
+ l = l.replace(/^\s*#\s?/, "");
230
+ out.push(l.replace(/\s+$/, ""));
231
+ }
232
+ while (out.length > 0 && out[0].trim() === "") out.shift();
233
+ while (out.length > 0 && out[out.length - 1].trim() === "") out.pop();
234
+ return out.join("\n").trim();
235
+ }
236
+
237
+ // src/lsp/hover_cache.ts
238
+ var CACHE_BYTE_CEILING = 4 * 1024 * 1024;
239
+ var CACHE_ENTRY_CEILING = 8;
240
+ var EXCLUDED_LANGUAGES = {
241
+ lua: "ASTEngine.parseRaw gives lua a private parser and grammar per parse so external-scanner state cannot leak between files (engine.ts parseRaw); a retained lua tree would carry that state into the next request"
242
+ };
243
+ var entries = /* @__PURE__ */ new Map();
244
+ var retainedBytes = 0;
245
+ var tick = 0;
246
+ var hits = 0;
247
+ var misses = 0;
248
+ var bypasses = 0;
249
+ var evictionsBytes = 0;
250
+ var evictionsEntries = 0;
251
+ function cacheStats() {
252
+ return {
253
+ entries: entries.size,
254
+ retainedBytes,
255
+ byteCeiling: CACHE_BYTE_CEILING,
256
+ entryCeiling: CACHE_ENTRY_CEILING,
257
+ hits,
258
+ misses,
259
+ bypasses,
260
+ evictionsBytes,
261
+ evictionsEntries,
262
+ excludedLanguages: { ...EXCLUDED_LANGUAGES }
263
+ };
264
+ }
265
+ function evict() {
266
+ while (entries.size > CACHE_ENTRY_CEILING || retainedBytes > CACHE_BYTE_CEILING) {
267
+ let oldestKey = null;
268
+ let oldest = Number.POSITIVE_INFINITY;
269
+ for (const [k, v] of entries) {
270
+ if (v.lastUsed < oldest) {
271
+ oldest = v.lastUsed;
272
+ oldestKey = k;
273
+ }
274
+ }
275
+ if (oldestKey === null) break;
276
+ const dropped = entries.get(oldestKey);
277
+ try {
278
+ if (dropped.tree && typeof dropped.tree.delete === "function") dropped.tree.delete();
279
+ } catch {
280
+ }
281
+ entries.delete(oldestKey);
282
+ retainedBytes -= dropped.bytes;
283
+ if (retainedBytes < 0) retainedBytes = 0;
284
+ if (entries.size > CACHE_ENTRY_CEILING) evictionsEntries++;
285
+ else evictionsBytes++;
286
+ }
287
+ }
288
+ async function parseForCursor(filePath, content) {
289
+ const contentHash = hashFileContent(content);
290
+ const bytes = Buffer.byteLength(content, "utf8");
291
+ const detected = languageFor(filePath, content);
292
+ if (detected !== null && EXCLUDED_LANGUAGES[detected] !== void 0) {
293
+ bypasses++;
294
+ return guardedParse(filePath, content, contentHash, bytes, "cache-bypass");
295
+ }
296
+ const entry = entries.get(contentHash);
297
+ if (entry !== void 0) {
298
+ hits++;
299
+ tick++;
300
+ entry.lastUsed = tick;
301
+ return { tree: entry.tree, language: entry.language, source: "cache-hit", contentHash, bytes, cache: cacheStats() };
302
+ }
303
+ misses++;
304
+ return guardedParse(filePath, content, contentHash, bytes, "fresh");
305
+ }
306
+ var ParseUnavailable = class extends Error {
307
+ /** Why the file could not be parsed, verbatim from the engine. */
308
+ reason;
309
+ constructor(reason) {
310
+ super(`the file could not be parsed: ${reason}`);
311
+ this.name = "ParseUnavailable";
312
+ this.reason = reason;
313
+ }
314
+ };
315
+ async function guardedParse(filePath, content, contentHash, bytes, source) {
316
+ let parsed;
317
+ try {
318
+ parsed = await ASTEngine.parseTree(filePath, content);
319
+ } catch (error) {
320
+ throw new ParseUnavailable(error instanceof Error ? error.message : String(error));
321
+ }
322
+ const { language, tree } = parsed;
323
+ if (source === "fresh") {
324
+ entries.set(contentHash, { hash: contentHash, language, bytes, tree, lastUsed: ++tick });
325
+ retainedBytes += bytes;
326
+ evict();
327
+ }
328
+ return { tree, language, source, contentHash, bytes, cache: cacheStats() };
329
+ }
330
+ function languageFor(filePath, content) {
331
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
332
+ if (ext === ".lua") return "lua";
333
+ if (content.startsWith("#!")) return "lua";
334
+ return null;
335
+ }
336
+ function occurrenceAtPoint(tree, line, byteColumn) {
337
+ if (!Number.isInteger(line) || line < 1 || !Number.isInteger(byteColumn) || byteColumn < 1) return null;
338
+ let node = tree.rootNode.descendantForPosition({ row: line - 1, column: byteColumn - 1 });
339
+ while (node !== null && node !== void 0) {
340
+ const type = node.type;
341
+ if (!isCommentNodeType(type) && node.isNamed && isIdentifierNodeType(type) && node.namedChildCount === 0) {
342
+ const text = String(node.text ?? "");
343
+ if (text.length > 0) {
344
+ return {
345
+ text,
346
+ startLine: node.startPosition.row + 1,
347
+ startColumn: node.startPosition.column + 1,
348
+ endColumn: node.endPosition.column + 1
349
+ };
350
+ }
351
+ }
352
+ node = node.parent;
353
+ }
354
+ return null;
355
+ }
356
+ function docCommentAt(tree, declarationLine) {
357
+ if (!tree || declarationLine === null || declarationLine === void 0) return "";
358
+ const targetRow = declarationLine - 2;
359
+ if (targetRow < 0) return "";
360
+ let found = null;
361
+ const visit = (n) => {
362
+ if (found !== null || n === null || n === void 0) return;
363
+ const startRow = Number(n.startPosition?.row ?? 0);
364
+ const endRow = Number(n.endPosition?.row ?? 0);
365
+ if (startRow > targetRow || endRow < targetRow) return;
366
+ if (isCommentNodeType(String(n.type))) {
367
+ if (endRow === targetRow) found = stripCommentMarkup(String(n.text ?? ""));
368
+ return;
369
+ }
370
+ const kids = n.children ?? [];
371
+ for (let i = 0; i < kids.length; i++) visit(kids[i]);
372
+ };
373
+ visit(tree.rootNode);
374
+ return found ?? "";
375
+ }
376
+
377
+ // src/lsp/type_inference_surface.ts
378
+ import { readFileSync } from "fs";
379
+ var ENGINE_CONFIDENCE_FLOOR = 0.9;
380
+ var MAX_WITNESS_BYTES = 48 * 1024;
381
+ var STRING_NODES = /* @__PURE__ */ new Set([
382
+ "string",
383
+ "string_literal",
384
+ "template_string",
385
+ "string_fragment",
386
+ "raw_string",
387
+ "verbatim_string_literal",
388
+ "interpolated_string_expression",
389
+ "char_literal"
390
+ ]);
391
+ var NUMBER_NODES = /* @__PURE__ */ new Set([
392
+ "number",
393
+ "integer_literal",
394
+ "float_literal",
395
+ "decimal_integer_literal",
396
+ "decimal_floating_point_literal",
397
+ "hex_integer_literal",
398
+ "int_literal",
399
+ "float_literal_node",
400
+ "integer"
401
+ ]);
402
+ var BOOLEAN_NODES = /* @__PURE__ */ new Set(["true", "false", "boolean", "bool_literal", "true_literal", "false_literal"]);
403
+ var RETURN_NODES = /* @__PURE__ */ new Set(["return_statement", "return_expression", "return_token", "yield_statement"]);
404
+ var CALLABLE_NODES = /* @__PURE__ */ new Set([
405
+ "function_declaration",
406
+ "function_definition",
407
+ "function_item",
408
+ "method_declaration",
409
+ "method_definition",
410
+ "method",
411
+ "constructor_declaration",
412
+ "singleton_method",
413
+ "arrow_function",
414
+ "function_expression",
415
+ "function",
416
+ "def",
417
+ "defn",
418
+ "fn",
419
+ "func",
420
+ "local_function_statement",
421
+ "function_signature",
422
+ "subroutine"
423
+ ]);
424
+ var ENGINE_DECL_TYPES = [
425
+ "variable_declaration",
426
+ "let_declaration",
427
+ "const_declaration",
428
+ "var_declaration",
429
+ "local_variable_declaration",
430
+ "assignment_statement"
431
+ ];
432
+ var ENGINE_FUNC_TYPES = [
433
+ "function_declaration",
434
+ "function_definition",
435
+ "method_declaration",
436
+ "def",
437
+ "defn",
438
+ "fn",
439
+ "func"
440
+ ];
441
+ function literalKindOf(node) {
442
+ if (node === null || node === void 0) return "valueless";
443
+ const t = node.type;
444
+ if (STRING_NODES.has(t)) return "string";
445
+ if (NUMBER_NODES.has(t)) return "number";
446
+ if (BOOLEAN_NODES.has(t)) return "boolean";
447
+ return null;
448
+ }
449
+ function kindForCandidate(candidate) {
450
+ const c = candidate.trim();
451
+ const table = {
452
+ string: "string",
453
+ String: "string",
454
+ number: "number",
455
+ int: "number",
456
+ long: "number",
457
+ double: "number",
458
+ float: "number",
459
+ i32: "number",
460
+ i64: "number",
461
+ f32: "number",
462
+ f64: "number",
463
+ decimal: "number",
464
+ boolean: "boolean",
465
+ bool: "boolean"
466
+ };
467
+ return Object.prototype.hasOwnProperty.call(table, c) ? table[c] : null;
468
+ }
469
+ function collectFacts(root) {
470
+ const nodeTypes = /* @__PURE__ */ new Set();
471
+ const callables = [];
472
+ const returns = [];
473
+ const stack = [root];
474
+ while (stack.length > 0) {
475
+ const n = stack.pop();
476
+ nodeTypes.add(n.type);
477
+ if (CALLABLE_NODES.has(n.type)) callables.push(n);
478
+ if (RETURN_NODES.has(n.type)) {
479
+ const value = n.childForFieldName?.("expression") ?? n.childForFieldName?.("value") ?? n.childForFieldName?.("result") ?? n.namedChildren?.find((c) => c !== null && c.type !== "return" && c.type !== "yield") ?? null;
480
+ returns.push({ kind: literalKindOf(value), node: n });
481
+ }
482
+ const children = n.children ?? [];
483
+ for (let i = children.length - 1; i >= 0; i--) if (children[i]) stack.push(children[i]);
484
+ }
485
+ return { nodeTypes, callables, returns };
486
+ }
487
+ function locateByStoredRange(callables, lineStart, lineEnd) {
488
+ let best = null;
489
+ let bestWidth = Number.MAX_SAFE_INTEGER;
490
+ for (const n of callables) {
491
+ const s = n.startPosition.row + 1;
492
+ const e = n.endPosition.row + 1;
493
+ if (s > lineStart || e < lineEnd) continue;
494
+ const width = e - s;
495
+ if (width < bestWidth) {
496
+ bestWidth = width;
497
+ best = n;
498
+ }
499
+ }
500
+ return best;
501
+ }
502
+ function storedReturnType(raw) {
503
+ const t = (raw ?? "").trim();
504
+ return t === "" ? null : t;
505
+ }
506
+ function unknownLine(reason, withheld, engine, witness = null) {
507
+ return {
508
+ provenance: "unknown",
509
+ text: UNRESOLVED_TYPE,
510
+ confidence: null,
511
+ reason,
512
+ withheld,
513
+ withheldText: engine === null ? null : engine.name,
514
+ storedProvenanceUnknown: false,
515
+ stale: false,
516
+ witness
517
+ };
518
+ }
519
+ function renderTypeLine(line) {
520
+ const staleMark = line.stale && line.provenance !== "unknown" ? " \u26A0\uFE0F stale" : "";
521
+ if (line.provenance === "inferred") {
522
+ const conf = line.confidence === null ? "n/a" : String(line.confidence);
523
+ return `type: ${line.text} _(inferred \xB7 engine confidence ${conf} \xB7 ${line.reason})_${staleMark}`;
524
+ }
525
+ if (line.provenance === "stored") {
526
+ const origin = line.storedProvenanceUnknown ? "its explicit-vs-inferred origin is not recorded" : "origin recorded";
527
+ return `type: ${line.text} _(stored in the bb index \xB7 ${origin})_${staleMark}`;
528
+ }
529
+ return `type: ${line.text} _(not resolved \u2014 ${line.reason})_`;
530
+ }
531
+ async function resolveTypeLine(filePath, content, symbol, raw) {
532
+ const stored = storedReturnType(raw);
533
+ const language = symbol.language ?? null;
534
+ if (!CALLABLE_SYMBOL_TYPES.has((symbol.symbol_type ?? "").toLowerCase())) {
535
+ return {
536
+ line: unknownLine("not a callable symbol; no return type exists to report", "not-a-function-symbol", null),
537
+ engine: null,
538
+ engineCoverage: { language: language ?? "unknown", covered: false, matchedNodeTypes: [] }
539
+ };
540
+ }
541
+ if (symbol.line_start === null || symbol.line_end === null) {
542
+ return {
543
+ line: unknownLine("the index stored no line range for this symbol, so nothing can be witnessed", "symbol-not-locatable-in-tree", null),
544
+ engine: null,
545
+ engineCoverage: { language: language ?? "unknown", covered: false, matchedNodeTypes: [] }
546
+ };
547
+ }
548
+ if (stored !== null) {
549
+ return {
550
+ line: {
551
+ provenance: "stored",
552
+ text: stored,
553
+ confidence: null,
554
+ reason: "type as stored in the index; the index records no marker of whether the analyzer read it from an explicit annotation or inferred it, so its own provenance is unstated",
555
+ withheld: null,
556
+ withheldText: null,
557
+ // Declared, measured: the column mixes explicit and inferred values
558
+ // indistinguishably. Reported on the wire, never resolved by guessing.
559
+ storedProvenanceUnknown: true,
560
+ stale: false,
561
+ witness: null
562
+ },
563
+ engine: null,
564
+ engineCoverage: { language: language ?? "unknown", covered: false, matchedNodeTypes: [] }
565
+ };
566
+ }
567
+ return await inferWithWitness(filePath, content, symbol, language, symbol.line_start, symbol.line_end);
568
+ }
569
+ var CALLABLE_SYMBOL_TYPES = /* @__PURE__ */ new Set([
570
+ "function",
571
+ "method",
572
+ "constructor",
573
+ "procedure",
574
+ "subroutine",
575
+ "getter",
576
+ "setter"
577
+ ]);
578
+ async function inferWithWitness(filePath, content, symbol, language, symStart, symEnd) {
579
+ let facts = null;
580
+ let engineTypes = null;
581
+ let tree = null;
582
+ if (Buffer.byteLength(content, "utf8") > MAX_WITNESS_BYTES) {
583
+ const kib = (Buffer.byteLength(content, "utf8") / 1024).toFixed(0);
584
+ return {
585
+ line: unknownLine(
586
+ `the file is ${kib} KiB, over the declared ${MAX_WITNESS_BYTES / 1024} KiB ceiling for witnessing (measured: p95 rises past the 50 ms hover budget above that size), so no type was inferred`,
587
+ "file-too-large-for-witness",
588
+ null
589
+ ),
590
+ engine: null,
591
+ engineCoverage: { language: language ?? "unknown", covered: false, matchedNodeTypes: [] }
592
+ };
593
+ }
594
+ try {
595
+ const parsed = await ASTEngine.parseTree(filePath, content);
596
+ tree = parsed.tree;
597
+ facts = collectFacts(tree.rootNode);
598
+ const engine = new TypeInference.TypeInferenceEngine();
599
+ engineTypes = engine.inferTypesForFile(tree.rootNode, content, filePath, language ?? "unknown");
600
+ } catch {
601
+ tree?.delete?.();
602
+ return {
603
+ line: unknownLine("the file could not be parsed, so no type was witnessed", "parse-failed", null),
604
+ engine: null,
605
+ engineCoverage: { language: language ?? "unknown", covered: false, matchedNodeTypes: [] }
606
+ };
607
+ }
608
+ try {
609
+ const matched = [...facts.nodeTypes].filter(
610
+ (t) => ENGINE_FUNC_TYPES.includes(t) || ENGINE_DECL_TYPES.includes(t)
611
+ );
612
+ const coverage = { language: language ?? "unknown", covered: matched.length > 0, matchedNodeTypes: matched.sort() };
613
+ const candidate = engineTypes.get(`${symbol.name}:return`);
614
+ const answer = candidate === void 0 ? null : { name: candidate.name, confidence: candidate.confidence, kind: candidate.kind, inferredAtCount: candidate.inferredAt.length };
615
+ if (!coverage.covered) {
616
+ return {
617
+ line: unknownLine(
618
+ `the inference engine has no node-type coverage for ${coverage.language}; it returned nothing rather than a type`,
619
+ "language-not-covered",
620
+ answer
621
+ ),
622
+ engine: answer,
623
+ engineCoverage: coverage
624
+ };
625
+ }
626
+ if (answer === null) {
627
+ return {
628
+ line: unknownLine("the engine returned no entry for this function; no type is asserted", "no-engine-answer", null),
629
+ engine: null,
630
+ engineCoverage: coverage
631
+ };
632
+ }
633
+ const node = locateByStoredRange(facts.callables, symStart, symEnd);
634
+ if (node === null) {
635
+ return {
636
+ line: unknownLine("no callable node in the tree contains this symbol's stored line range", "symbol-not-locatable-in-tree", answer),
637
+ engine: answer,
638
+ engineCoverage: coverage
639
+ };
640
+ }
641
+ const own = facts.returns.filter((r) => {
642
+ const s = r.node.startPosition.row + 1;
643
+ const e = r.node.endPosition.row + 1;
644
+ return s >= node.startPosition.row + 1 && e <= node.endPosition.row + 1;
645
+ });
646
+ const kinds = [...new Set(own.map((r) => r.kind).filter((k) => k !== null))].sort();
647
+ const witness = {
648
+ nodeType: node.type,
649
+ startLine: node.startPosition.row + 1,
650
+ endLine: node.endPosition.row + 1,
651
+ returnExpressionsSeen: own.length,
652
+ returnKinds: kinds,
653
+ corroborated: false
654
+ };
655
+ const isVoidCandidate = answer.name === "void" || answer.kind === TypeInference.TypeKind.Void;
656
+ const hasValuelessReturn = own.length > 0 && own.every((r) => r.kind === "valueless");
657
+ const annotatesVoid = /(^|[^A-Za-z0-9_])(void|Unit)($|[^A-Za-z0-9_])/.test(stripDeclarationHead(node.text));
658
+ if (isVoidCandidate && !(hasValuelessReturn || annotatesVoid)) {
659
+ return {
660
+ line: unknownLine(
661
+ "the engine answered `void` at high confidence, but the tree shows no valueless return and no explicit void annotation \u2014 that answer means the grammar's return form was not recognized, so it is withheld",
662
+ "engine-reported-void-without-valueless-return",
663
+ answer,
664
+ witness
665
+ ),
666
+ engine: answer,
667
+ engineCoverage: coverage
668
+ };
669
+ }
670
+ if (isVoidCandidate && (hasValuelessReturn || annotatesVoid)) {
671
+ witness.corroborated = true;
672
+ return {
673
+ line: {
674
+ provenance: "inferred",
675
+ text: answer.name,
676
+ confidence: answer.confidence,
677
+ reason: `witnessed in the tree: ${witness.returnExpressionsSeen} valueless return expression(s) at ${witness.nodeType}`,
678
+ withheld: null,
679
+ withheldText: null,
680
+ storedProvenanceUnknown: false,
681
+ stale: false,
682
+ witness
683
+ },
684
+ engine: answer,
685
+ engineCoverage: coverage
686
+ };
687
+ }
688
+ const wanted = kindForCandidate(answer.name);
689
+ if (wanted === null) {
690
+ return {
691
+ line: unknownLine(
692
+ `the engine proposed \`${answer.name}\`, which is outside the narrow proven subset (a bare primitive literal type only)`,
693
+ "no-witness-in-tree",
694
+ answer,
695
+ witness
696
+ ),
697
+ engine: answer,
698
+ engineCoverage: coverage
699
+ };
700
+ }
701
+ if (kinds.length !== 1 || kinds[0] !== wanted) {
702
+ return {
703
+ line: unknownLine(
704
+ `the engine proposed \`${answer.name}\` but the tree's return expressions read ${kinds.length === 0 ? "no recognizable literal" : kinds.join(" | ")}`,
705
+ "witness-disagrees-with-engine",
706
+ answer,
707
+ witness
708
+ ),
709
+ engine: answer,
710
+ engineCoverage: coverage
711
+ };
712
+ }
713
+ witness.corroborated = true;
714
+ if (answer.confidence < ENGINE_CONFIDENCE_FLOOR) {
715
+ return {
716
+ line: {
717
+ ...unknownLine(
718
+ `witnessed in the tree, but the engine's own confidence ${answer.confidence} is below the declared floor ${ENGINE_CONFIDENCE_FLOOR}`,
719
+ "engine-confidence-below-floor",
720
+ answer,
721
+ witness
722
+ ),
723
+ witness
724
+ },
725
+ engine: answer,
726
+ engineCoverage: coverage
727
+ };
728
+ }
729
+ return {
730
+ line: {
731
+ provenance: "inferred",
732
+ text: answer.name,
733
+ confidence: answer.confidence,
734
+ reason: `witnessed in the tree: ${witness.returnExpressionsSeen} return expression(s) of kind ${kinds[0]} at ${witness.nodeType}`,
735
+ withheld: null,
736
+ withheldText: null,
737
+ storedProvenanceUnknown: false,
738
+ stale: false,
739
+ witness
740
+ },
741
+ engine: answer,
742
+ engineCoverage: coverage
743
+ };
744
+ } finally {
745
+ tree?.delete?.();
746
+ }
747
+ }
748
+ function stripDeclarationHead(text) {
749
+ const i = text.indexOf("{");
750
+ return i === -1 ? text : text.slice(0, i);
751
+ }
752
+
753
+ // src/lsp/rich_hover.ts
754
+ import { readFileSync as readFileSync3, statSync } from "fs";
755
+
756
+ // src/lsp/navigation.ts
757
+ import { readFileSync as readFileSync2 } from "fs";
758
+
759
+ // src/lsp/launch_languages.ts
760
+ import path from "path";
761
+ var LAUNCH_LANGUAGES = [
762
+ "java",
763
+ "groovy",
764
+ "kotlin",
765
+ "scala",
766
+ "typescript",
767
+ "javascript",
768
+ "python",
769
+ "zig",
770
+ "go",
771
+ "rust",
772
+ "c",
773
+ "cpp",
774
+ "vue",
775
+ "svelte",
776
+ // M29 wave 3 (owner-approved 2026-11-08): the remaining analyzers. Each
777
+ // language joins the launch set only through the wave protocol; the wave
778
+ // execution gate reports which of them can actually serve hover/definition/
779
+ // references today and which are blocked on an analyzer gap.
780
+ "tsx",
781
+ "html",
782
+ "css",
783
+ "scss",
784
+ "sass",
785
+ "json",
786
+ "yaml",
787
+ "toml",
788
+ "markdown",
789
+ "xml",
790
+ "graphql",
791
+ "protobuf",
792
+ "sql",
793
+ "perl",
794
+ "ruby",
795
+ "php",
796
+ "lua",
797
+ "elixir",
798
+ "erlang",
799
+ "haskell",
800
+ "clojure",
801
+ "julia",
802
+ "r",
803
+ "bash",
804
+ "powershell",
805
+ "make",
806
+ "swift",
807
+ "dart",
808
+ "csharp",
809
+ "fsharp",
810
+ "nim",
811
+ "vb",
812
+ "vba",
813
+ "matlab",
814
+ "clean",
815
+ "solidity",
816
+ "hcl",
817
+ "terraform"
818
+ ];
819
+ function isLaunchLanguage(language) {
820
+ return language !== null && LAUNCH_LANGUAGES.includes(language);
821
+ }
822
+ function languageForPath(filePath) {
823
+ const ext = path.extname(filePath).toLowerCase();
824
+ const map = {
825
+ ".java": "java",
826
+ ".groovy": "groovy",
827
+ ".gradle": "groovy",
828
+ ".kt": "kotlin",
829
+ ".kts": "kotlin",
830
+ ".scala": "scala",
831
+ ".sc": "scala",
832
+ ".ts": "typescript",
833
+ ".js": "javascript",
834
+ ".jsx": "javascript",
835
+ ".mjs": "javascript",
836
+ ".cjs": "javascript",
837
+ ".py": "python",
838
+ ".zig": "zig",
839
+ ".go": "go",
840
+ ".rs": "rust",
841
+ ".c": "c",
842
+ ".h": "c",
843
+ ".cpp": "cpp",
844
+ ".cc": "cpp",
845
+ ".cxx": "cpp",
846
+ ".hpp": "cpp",
847
+ ".vue": "vue",
848
+ ".svelte": "svelte",
849
+ // M29 wave 3 — extension-level identity for the onboarded languages.
850
+ // Deliberately ABSENT: ".m" (objc and matlab share it, so it resolves to
851
+ // neither until content sniffing lands — a declared absence, not a guess).
852
+ ".tsx": "tsx",
853
+ ".html": "html",
854
+ ".htm": "html",
855
+ ".css": "css",
856
+ ".scss": "scss",
857
+ ".sass": "sass",
858
+ ".json": "json",
859
+ ".yaml": "yaml",
860
+ ".yml": "yaml",
861
+ ".toml": "toml",
862
+ ".md": "markdown",
863
+ ".markdown": "markdown",
864
+ ".mdx": "markdown",
865
+ ".xml": "xml",
866
+ ".graphql": "graphql",
867
+ ".gql": "graphql",
868
+ ".proto": "protobuf",
869
+ ".sql": "sql",
870
+ ".pl": "perl",
871
+ ".pm": "perl",
872
+ ".rb": "ruby",
873
+ ".php": "php",
874
+ ".lua": "lua",
875
+ ".ex": "elixir",
876
+ ".exs": "elixir",
877
+ ".erl": "erlang",
878
+ ".hrl": "erlang",
879
+ ".hs": "haskell",
880
+ ".clj": "clojure",
881
+ ".cljs": "clojure",
882
+ ".cljc": "clojure",
883
+ ".jl": "julia",
884
+ ".r": "r",
885
+ ".sh": "bash",
886
+ ".bash": "bash",
887
+ ".ps1": "powershell",
888
+ ".psm1": "powershell",
889
+ ".mk": "make",
890
+ ".swift": "swift",
891
+ ".dart": "dart",
892
+ ".cs": "csharp",
893
+ ".fs": "fsharp",
894
+ ".fsi": "fsharp",
895
+ ".fsx": "fsharp",
896
+ ".nim": "nim",
897
+ ".vb": "vb",
898
+ ".bas": "vba",
899
+ ".vba": "vba",
900
+ ".icl": "clean",
901
+ ".dcl": "clean",
902
+ ".sol": "solidity",
903
+ ".hcl": "hcl",
904
+ ".tf": "terraform",
905
+ ".tfvars": "terraform"
906
+ };
907
+ return map[ext] ?? null;
908
+ }
909
+
910
+ // src/lsp/navigation.ts
911
+ var MAX_LOCATIONS = 200;
912
+ var DECLARATION_ENTRY_NOTE = "this is the symbol's own declaration, served because the client asked for it (not a usage)";
913
+ function languageAbsence(filePath) {
914
+ const language = languageForPath(filePath);
915
+ if (isLaunchLanguage(language)) return null;
916
+ return {
917
+ reason: "language_not_launched",
918
+ language,
919
+ detail: language === null ? `file type not in the launch language set (launch: ${LAUNCH_LANGUAGES.join(", ")})` : `${language} is not a launch language (launch: ${LAUNCH_LANGUAGES.join(", ")})`,
920
+ searchedIndex: false
921
+ };
922
+ }
923
+ function notIndexedAbsence(filePath) {
924
+ return {
925
+ reason: "not_indexed",
926
+ language: languageForPath(filePath),
927
+ detail: "no indexed symbol at this position",
928
+ searchedIndex: true
929
+ };
930
+ }
931
+ function findIndexedSymbol(raw, filePath, line, character) {
932
+ let sourceText;
933
+ try {
934
+ sourceText = readFileSync2(filePath, "utf8");
935
+ } catch {
936
+ return null;
937
+ }
938
+ const mapper = createPositionMapper(sourceText);
939
+ let indexPos;
940
+ try {
941
+ indexPos = mapper.lspToIndex({ line, character });
942
+ } catch (err) {
943
+ if (err instanceof PositionError) return null;
944
+ throw err;
945
+ }
946
+ const rows = raw.prepare(
947
+ `SELECT s.id, s.name, s.qualified_name, s.symbol_type, s.file_id,
948
+ s.byte_start, s.byte_end, s.line_start, s.line_end,
949
+ f.path, f.language
950
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
951
+ WHERE f.path = ? AND s.line_start IS NOT NULL AND s.line_end IS NOT NULL
952
+ AND s.line_start <= ? AND s.line_end >= ?`
953
+ ).all(filePath, indexPos.line, indexPos.line);
954
+ let best = null;
955
+ let bestSpan = Number.MAX_SAFE_INTEGER;
956
+ for (const row of rows) {
957
+ if (row.byte_start !== null && row.byte_end !== null && row.line_start === indexPos.line && indexPos.column >= row.byte_start && indexPos.column < row.byte_end) {
958
+ return row;
959
+ }
960
+ const span = (row.line_end ?? 0) - (row.line_start ?? 0);
961
+ if (best === null || span < bestSpan) {
962
+ best = row;
963
+ bestSpan = span;
964
+ }
965
+ }
966
+ return best;
967
+ }
968
+ function findNamedSymbolAtPosition(raw, filePath, line, character) {
969
+ let sourceText;
970
+ try {
971
+ sourceText = readFileSync2(filePath, "utf8");
972
+ } catch {
973
+ return null;
974
+ }
975
+ const mapper = createPositionMapper(sourceText);
976
+ let indexPos;
977
+ try {
978
+ indexPos = mapper.lspToIndex({ line, character });
979
+ } catch (err) {
980
+ if (err instanceof PositionError) return null;
981
+ throw err;
982
+ }
983
+ let jsCol;
984
+ try {
985
+ jsCol = mapper.indexToLsp({ line: indexPos.line, column: indexPos.column }).character;
986
+ } catch (err) {
987
+ if (err instanceof PositionError) return null;
988
+ throw err;
989
+ }
990
+ const lineText = mapper.lineTexts[indexPos.line - 1] ?? "";
991
+ if (jsCol > lineText.length) return null;
992
+ const isWordChar3 = (ch) => /[A-Za-z0-9_$]/.test(ch);
993
+ if (jsCol < lineText.length && !isWordChar3(lineText[jsCol])) {
994
+ if (jsCol === 0 || !isWordChar3(lineText[jsCol - 1])) return null;
995
+ jsCol--;
996
+ }
997
+ let start = jsCol;
998
+ let end = jsCol;
999
+ while (start > 0 && isWordChar3(lineText[start - 1])) start--;
1000
+ while (end < lineText.length && isWordChar3(lineText[end])) end++;
1001
+ const identifier = lineText.slice(start, end);
1002
+ if (!identifier) return null;
1003
+ const sameFile = raw.prepare(
1004
+ `SELECT s.id, s.name, s.qualified_name, s.symbol_type, s.file_id,
1005
+ s.byte_start, s.byte_end, s.line_start, s.line_end,
1006
+ f.path, f.language
1007
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
1008
+ WHERE f.path = ? AND s.name = ?`
1009
+ ).all(filePath, identifier);
1010
+ if (sameFile.length === 1) return sameFile[0];
1011
+ if (sameFile.length > 1) {
1012
+ const hit = sameFile.find(
1013
+ (r) => r.line_start !== null && r.line_end !== null && r.line_start <= indexPos.line && r.line_end >= indexPos.line
1014
+ );
1015
+ if (hit) return hit;
1016
+ }
1017
+ const rows = raw.prepare(
1018
+ `SELECT s.id, s.name, s.qualified_name, s.symbol_type, s.file_id,
1019
+ s.byte_start, s.byte_end, s.line_start, s.line_end,
1020
+ f.path, f.language
1021
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
1022
+ WHERE s.name = ?`
1023
+ ).all(identifier);
1024
+ if (rows.length === 1) return rows[0];
1025
+ return null;
1026
+ }
1027
+ function resolveDefinition(raw, request) {
1028
+ const languageGate = languageAbsence(request.filePath);
1029
+ if (languageGate !== null) {
1030
+ return {
1031
+ found: false,
1032
+ location: null,
1033
+ symbol: null,
1034
+ evidenceTier: "not_indexed",
1035
+ absence: languageGate
1036
+ };
1037
+ }
1038
+ const named = findNamedSymbolAtPosition(raw, request.filePath, request.line, request.character);
1039
+ const symbol = named ?? findIndexedSymbol(raw, request.filePath, request.line, request.character);
1040
+ if (symbol === null) {
1041
+ return {
1042
+ found: false,
1043
+ location: null,
1044
+ symbol: null,
1045
+ evidenceTier: "not_indexed",
1046
+ absence: notIndexedAbsence(request.filePath)
1047
+ };
1048
+ }
1049
+ const location = symbolLocation(symbol);
1050
+ if (location === null) {
1051
+ return {
1052
+ found: false,
1053
+ location: null,
1054
+ symbol: {
1055
+ id: symbol.id,
1056
+ name: symbol.name,
1057
+ qualifiedName: symbol.qualified_name,
1058
+ symbolType: symbol.symbol_type
1059
+ },
1060
+ evidenceTier: "indexed",
1061
+ absence: {
1062
+ reason: "unresolvable_location",
1063
+ language: symbol.language ?? languageForPath(request.filePath),
1064
+ detail: `symbol ${symbol.name} is indexed at lines ${symbol.line_start}..${symbol.line_end} but its stored byte columns do not convert against the source on disk`,
1065
+ searchedIndex: true
1066
+ }
1067
+ };
1068
+ }
1069
+ return {
1070
+ found: true,
1071
+ location,
1072
+ symbol: {
1073
+ id: symbol.id,
1074
+ name: symbol.name,
1075
+ qualifiedName: symbol.qualified_name,
1076
+ symbolType: symbol.symbol_type
1077
+ },
1078
+ evidenceTier: "indexed",
1079
+ absence: null
1080
+ };
1081
+ }
1082
+ function symbolLocation(symbol) {
1083
+ if (symbol.byte_start === null || symbol.byte_end === null || symbol.line_start === null || symbol.line_end === null || symbol.path === null) {
1084
+ return null;
1085
+ }
1086
+ let sourceText;
1087
+ try {
1088
+ sourceText = readFileSync2(symbol.path, "utf8");
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ const startCol = resolveRowByteColumns(sourceText, symbol.line_start, symbol.byte_start);
1093
+ const endCol = resolveRowByteColumns(sourceText, symbol.line_end, symbol.byte_end);
1094
+ if (startCol === null || endCol === null) return null;
1095
+ const mapper = createPositionMapper(sourceText);
1096
+ const range = mapper.indexRangeToLsp({
1097
+ start: { line: startCol.line, column: startCol.column },
1098
+ end: { line: endCol.line, column: endCol.column }
1099
+ });
1100
+ return {
1101
+ path: symbol.path,
1102
+ line: range.start.line,
1103
+ character: range.start.character,
1104
+ endLine: range.end.line,
1105
+ endCharacter: range.end.character
1106
+ };
1107
+ }
1108
+ function edgeLocation(path3, byteStart, byteEnd, lineStart, lineEnd, sourceCache) {
1109
+ if (path3 === null || byteStart === null || byteEnd === null || lineStart === null || lineEnd === null) {
1110
+ return null;
1111
+ }
1112
+ return edgeLocationDetailed(path3, byteStart, byteEnd, lineStart, lineEnd, sourceCache).location;
1113
+ }
1114
+ function edgeLocationDetailed(path3, byteStart, byteEnd, lineStart, lineEnd, sourceCache) {
1115
+ if (path3 === null || byteStart === null || byteEnd === null || lineStart === null || lineEnd === null) {
1116
+ return {
1117
+ location: null,
1118
+ absence: {
1119
+ reason: "unresolvable_location",
1120
+ language: null,
1121
+ detail: "stored relationship endpoint carries no complete range (path, line, or byte columns are null)",
1122
+ searchedIndex: true
1123
+ }
1124
+ };
1125
+ }
1126
+ let sourceText;
1127
+ let cachedMapper;
1128
+ let cachedLines;
1129
+ const cached = sourceCache?.get(path3);
1130
+ if (cached !== void 0 && "readError" in cached) {
1131
+ return {
1132
+ location: null,
1133
+ absence: {
1134
+ reason: "unresolvable_location",
1135
+ language: languageForPath(path3),
1136
+ detail: `source file could not be read (${cached.readError}); the stored line is omitted rather than emitted unverified`,
1137
+ searchedIndex: true
1138
+ }
1139
+ };
1140
+ } else if (cached !== void 0) {
1141
+ sourceText = cached.text;
1142
+ cachedLines = cached.lines;
1143
+ cachedMapper = cached.mapper;
1144
+ } else {
1145
+ try {
1146
+ sourceText = readFileSync2(path3, "utf8");
1147
+ const lines2 = sourceText.split("\n");
1148
+ sourceCache?.set(path3, { text: sourceText, lines: lines2 });
1149
+ } catch (error) {
1150
+ sourceCache?.set(path3, { readError: error.message });
1151
+ return {
1152
+ location: null,
1153
+ absence: {
1154
+ reason: "unresolvable_location",
1155
+ language: languageForPath(path3),
1156
+ detail: `source file could not be read (${error.message}); the stored line is omitted rather than emitted unverified`,
1157
+ searchedIndex: true
1158
+ }
1159
+ };
1160
+ }
1161
+ }
1162
+ const lines = cachedLines ?? sourceText.split("\n");
1163
+ const startCol = resolveRowByteColumns(sourceText, lineStart, byteStart, lines);
1164
+ const endCol = resolveRowByteColumns(sourceText, lineEnd, byteEnd, lines);
1165
+ if (startCol === null || endCol === null) {
1166
+ return {
1167
+ location: null,
1168
+ absence: {
1169
+ reason: "unresolvable_location",
1170
+ language: languageForPath(path3),
1171
+ detail: `stored byte columns (${byteStart}..${byteEnd} on lines ${lineStart}..${lineEnd}) do not convert against this source text`,
1172
+ searchedIndex: true
1173
+ }
1174
+ };
1175
+ }
1176
+ const mapper = cachedMapper ?? createPositionMapper(sourceText);
1177
+ if (cachedMapper === void 0 && cached !== void 0 && sourceCache !== void 0) {
1178
+ sourceCache.set(path3, { text: sourceText, lines, mapper });
1179
+ }
1180
+ let range;
1181
+ try {
1182
+ range = mapper.indexRangeToLsp({
1183
+ start: { line: startCol.line, column: startCol.column },
1184
+ end: { line: endCol.line, column: endCol.column }
1185
+ });
1186
+ } catch (error) {
1187
+ return {
1188
+ location: null,
1189
+ absence: {
1190
+ reason: "unresolvable_location",
1191
+ language: languageForPath(path3),
1192
+ detail: `stored range does not convert to an LSP range (${error.message})`,
1193
+ searchedIndex: true
1194
+ }
1195
+ };
1196
+ }
1197
+ return {
1198
+ location: {
1199
+ path: path3,
1200
+ line: range.start.line,
1201
+ character: range.start.character,
1202
+ endLine: range.end.line,
1203
+ endCharacter: range.end.character
1204
+ },
1205
+ absence: null
1206
+ };
1207
+ }
1208
+ function resolveReferences(raw, request) {
1209
+ const limit = Math.max(1, Math.min(request.limit ?? MAX_LOCATIONS, MAX_LOCATIONS));
1210
+ const languageGate = languageAbsence(request.filePath);
1211
+ if (languageGate !== null) {
1212
+ return referencesAbsent(languageGate, limit);
1213
+ }
1214
+ const symbol = findIndexedSymbol(raw, request.filePath, request.line, request.character);
1215
+ if (symbol === null) {
1216
+ return referencesAbsent({ ...notIndexedAbsence(request.filePath), totalCount: 0 }, limit);
1217
+ }
1218
+ const edges = raw.prepare(
1219
+ // M62 §2: the stored call-site line is now SELECTED — the surface has to
1220
+ // reach the call site, and the store's line is the only place a call site
1221
+ // is recorded. It arrives under one alias (`stored_call_line`) and its
1222
+ // ONLY use is the hand-off to the address owner below; nothing in this
1223
+ // module reads it for display. The source-side join is UNCHANGED: it is
1224
+ // still the calling symbol's own indexed range, which is what the
1225
+ // `declaration` substitute uses and what the caller declaration line
1226
+ // comes from.
1227
+ `SELECT r.id, r.source_symbol_id, r.target_symbol_id,
1228
+ r.confidence, r.relationship_type,
1229
+ ${STORED_LINE_SELECT},
1230
+ sf.path AS source_path, ss.byte_start AS source_byte_start,
1231
+ ss.byte_end AS source_byte_end, ss.line_start AS source_line_start,
1232
+ ss.line_end AS source_line_end,
1233
+ tf.path AS target_path, ts.byte_start AS target_byte_start,
1234
+ ts.byte_end AS target_byte_end, ts.line_start AS target_line_start,
1235
+ ts.line_end AS target_line_end
1236
+ FROM brain_relationship r
1237
+ LEFT JOIN brain_symbol ss ON ss.id = r.source_symbol_id
1238
+ LEFT JOIN brain_file sf ON sf.id = ss.file_id
1239
+ LEFT JOIN brain_symbol ts ON ts.id = r.target_symbol_id
1240
+ LEFT JOIN brain_file tf ON tf.id = ts.file_id
1241
+ WHERE r.target_symbol_id = ?
1242
+ ORDER BY r.confidence DESC, r.id`
1243
+ ).all(symbol.id);
1244
+ const locations = [];
1245
+ const seen = /* @__PURE__ */ new Set();
1246
+ let omittedUnresolvable = 0;
1247
+ let omittedUnpositionable = 0;
1248
+ let coalescedRows = 0;
1249
+ const sourceCache = /* @__PURE__ */ new Map();
1250
+ for (const edge of edges) {
1251
+ if (!isUsageSiteRow(
1252
+ {
1253
+ source_symbol_id: edge.source_symbol_id,
1254
+ target_symbol_id: edge.target_symbol_id,
1255
+ relationship_type: edge.relationship_type
1256
+ },
1257
+ symbol.id
1258
+ )) {
1259
+ continue;
1260
+ }
1261
+ const address = deriveCallSiteAddress({
1262
+ storedLine: storedLineFromRow(edge),
1263
+ // The address owner's `file` is documented as the calling symbol's
1264
+ // RELATIVE path, for the renderings the graph/hub/MCP surfaces produce.
1265
+ // This surface hands the editor absolute paths (brain_file.path), and it
1266
+ // holds no project root to relativise against, so the field carries the
1267
+ // same absolute string as `NavigationLocation.path`. DECLARED divergence,
1268
+ // not a silent substitution: a renderer printing this address gets an
1269
+ // absolute path, which is correct here and differs from the graph/hub
1270
+ // renderings. Reported as a gap in the owner's field contract.
1271
+ file: edge.source_path ?? "",
1272
+ // The ONLY permitted substitute, and the caller's own declaration line.
1273
+ callerDeclaration: { line: edge.source_line_start }
1274
+ });
1275
+ const placed = referenceLocation(edge, address, sourceCache);
1276
+ if (placed.location === null) {
1277
+ if (placed.omission === "unpositionable") omittedUnpositionable++;
1278
+ else omittedUnresolvable++;
1279
+ continue;
1280
+ }
1281
+ const loc = placed.location;
1282
+ const key = `${loc.path}:${loc.line}:${loc.character}`;
1283
+ if (seen.has(key)) {
1284
+ coalescedRows++;
1285
+ continue;
1286
+ }
1287
+ seen.add(key);
1288
+ if (locations.length < limit) {
1289
+ locations.push(loc);
1290
+ }
1291
+ }
1292
+ if (request.includeDeclaration) {
1293
+ const decl = symbolLocation(symbol);
1294
+ if (decl !== null && symbol.line_start !== null && symbol.path !== null) {
1295
+ const withAddress = {
1296
+ ...decl,
1297
+ callSite: {
1298
+ kind: "declaration",
1299
+ file: symbol.path,
1300
+ line: symbol.line_start,
1301
+ note: DECLARATION_ENTRY_NOTE,
1302
+ rowId: `declaration:${symbol.id}`
1303
+ }
1304
+ };
1305
+ const key = `${withAddress.path}:${withAddress.line}:declaration:${symbol.id}`;
1306
+ if (!seen.has(key)) {
1307
+ seen.add(key);
1308
+ if (locations.length < limit) {
1309
+ locations.push(withAddress);
1310
+ }
1311
+ }
1312
+ }
1313
+ }
1314
+ const totalCount = seen.size;
1315
+ return {
1316
+ locations,
1317
+ truncated: totalCount > locations.length,
1318
+ totalCount,
1319
+ limit,
1320
+ evidenceTier: "indexed",
1321
+ // Locations WERE served, so there is no absence — but the omission counts
1322
+ // are not absence markers and must not be dropped: a caller showing 3 of 5
1323
+ // rows needs to know the other two were dropped on purpose, and WHICH way.
1324
+ absence: null,
1325
+ omittedUnresolvable,
1326
+ omittedUnpositionable,
1327
+ coalescedRows
1328
+ };
1329
+ }
1330
+ function referenceLocation(edge, address, sourceCache) {
1331
+ if (address.kind === "unknown") return { location: null, omission: "unpositionable" };
1332
+ if (edge.source_path === null) return { location: null, omission: "unresolvable" };
1333
+ if (isExactAddress(address)) {
1334
+ const line0 = addressLine0(address);
1335
+ if (line0 === null) return { location: null, omission: "unpositionable" };
1336
+ const sourceText = readSourceCached(edge.source_path, sourceCache);
1337
+ if (sourceText === null) return { location: null, omission: "unresolvable" };
1338
+ if (line0 >= createPositionMapper(sourceText).lineTexts.length) {
1339
+ return { location: null, omission: "unresolvable" };
1340
+ }
1341
+ return {
1342
+ location: {
1343
+ path: edge.source_path,
1344
+ line: line0,
1345
+ character: 0,
1346
+ endLine: line0,
1347
+ endCharacter: 0,
1348
+ callSite: { ...address, rowId: edge.id }
1349
+ },
1350
+ omission: "none"
1351
+ };
1352
+ }
1353
+ const resolved = edgeLocationDetailed(
1354
+ edge.source_path,
1355
+ edge.source_byte_start,
1356
+ edge.source_byte_end,
1357
+ edge.source_line_start,
1358
+ edge.source_line_end
1359
+ );
1360
+ if (resolved.location === null) return { location: null, omission: "unresolvable" };
1361
+ return {
1362
+ location: { ...resolved.location, callSite: { ...address, rowId: edge.id } },
1363
+ omission: "none"
1364
+ };
1365
+ }
1366
+ function readSourceCached(path3, cache) {
1367
+ const hit = cache.get(path3);
1368
+ if (hit !== void 0) return hit;
1369
+ let text = null;
1370
+ try {
1371
+ text = readFileSync2(path3, "utf8");
1372
+ } catch {
1373
+ text = null;
1374
+ }
1375
+ cache.set(path3, text);
1376
+ return text;
1377
+ }
1378
+ function referencesAbsent(absence, limit) {
1379
+ return {
1380
+ locations: [],
1381
+ truncated: false,
1382
+ totalCount: 0,
1383
+ limit,
1384
+ evidenceTier: "not_indexed",
1385
+ // The count is restated INSIDE the absence, beside `searchedIndex`. A
1386
+ // client that reads the absence must be able to see "0 matches, and the
1387
+ // index was not searched for this language" as one fact; leaving the count
1388
+ // only on the result invites a reader to treat it as a workspace-wide
1389
+ // claim about a language this server never served.
1390
+ absence: { ...absence, totalCount: 0 },
1391
+ omittedUnresolvable: 0,
1392
+ omittedUnpositionable: 0,
1393
+ coalescedRows: 0
1394
+ };
1395
+ }
1396
+
1397
+ // src/lsp/enrichment.ts
1398
+ var ENRICHMENT_KEYS = /* @__PURE__ */ new Set([
1399
+ "role",
1400
+ "pattern",
1401
+ "layerFlow",
1402
+ "criticality",
1403
+ "frequency",
1404
+ "isTestCode",
1405
+ "isGeneratedCode",
1406
+ "isFrameworkContract",
1407
+ "isDependencyInjection",
1408
+ "inferredAt"
1409
+ ]);
1410
+ function parseEnrichment(metadata) {
1411
+ if (typeof metadata !== "string" || metadata.length === 0) return null;
1412
+ let parsed;
1413
+ try {
1414
+ parsed = JSON.parse(metadata);
1415
+ } catch {
1416
+ return null;
1417
+ }
1418
+ if (typeof parsed !== "object" || parsed === null) return null;
1419
+ if (typeof parsed["inferredAt"] !== "number") return null;
1420
+ const out = { inferredAt: parsed["inferredAt"] };
1421
+ for (const key of ENRICHMENT_KEYS) {
1422
+ if (key === "inferredAt") continue;
1423
+ const v = parsed[key];
1424
+ if (v === void 0 || v === null) continue;
1425
+ if (key === "role" || key === "pattern" || key === "criticality") {
1426
+ if (typeof v === "string") out[key] = v;
1427
+ } else if (key === "layerFlow" || key === "frequency") {
1428
+ if (typeof v === "string") out[key] = v;
1429
+ } else if (typeof v === "boolean") {
1430
+ ;
1431
+ out[key] = v;
1432
+ }
1433
+ }
1434
+ return out;
1435
+ }
1436
+ function enrichmentIsStale(enrichment, rowTimeUpdated) {
1437
+ if (typeof enrichment.inferredAt !== "number") return false;
1438
+ if (typeof rowTimeUpdated !== "number" || rowTimeUpdated <= 0) return false;
1439
+ return enrichment.inferredAt < rowTimeUpdated;
1440
+ }
1441
+ function describeFrequency(frequency) {
1442
+ const m = /^one_of_(\d+)$/.exec(frequency);
1443
+ if (m) {
1444
+ const n = Number(m[1]);
1445
+ return n === 0 ? "no referring rows" : n === 1 ? "1 referring row" : `${n} referring rows`;
1446
+ }
1447
+ return frequency;
1448
+ }
1449
+ function describeEnrichment(e) {
1450
+ const parts = [];
1451
+ if (e.role) parts.push(`role ${e.role}`);
1452
+ if (e.pattern) parts.push(`pattern ${e.pattern}`);
1453
+ if (e.layerFlow) parts.push(`layer ${e.layerFlow}`);
1454
+ if (e.frequency) parts.push(describeFrequency(e.frequency));
1455
+ if (e.criticality) parts.push(`${e.criticality} criticality`);
1456
+ const flags = [];
1457
+ if (e.isTestCode) flags.push("test code");
1458
+ if (e.isGeneratedCode) flags.push("generated");
1459
+ if (e.isFrameworkContract) flags.push("framework contract");
1460
+ if (e.isDependencyInjection) flags.push("dependency injection");
1461
+ if (flags.length > 0) parts.push(flags.join("+"));
1462
+ return parts.join(" \xB7 ");
1463
+ }
1464
+ function hasDisplayableEnrichment(e) {
1465
+ if (!e) return false;
1466
+ return describeEnrichment(e).length > 0;
1467
+ }
1468
+
1469
+ // src/lsp/rich_hover.ts
1470
+ var MAX_USAGE_LINKS = 3;
1471
+ var LINE_COUNT_CACHE_MAX = 512;
1472
+ var lineCountCache = /* @__PURE__ */ new Map();
1473
+ function cachedLineCount(path3) {
1474
+ let stat;
1475
+ try {
1476
+ const st = statSync(path3);
1477
+ stat = { mtimeMs: st.mtimeMs, size: st.size };
1478
+ } catch {
1479
+ return null;
1480
+ }
1481
+ const entry = lineCountCache.get(path3);
1482
+ if (entry === void 0) return null;
1483
+ if (entry.mtimeMs !== stat.mtimeMs || entry.size !== stat.size) {
1484
+ lineCountCache.delete(path3);
1485
+ return null;
1486
+ }
1487
+ lineCountCache.delete(path3);
1488
+ lineCountCache.set(path3, entry);
1489
+ return entry.lineCount;
1490
+ }
1491
+ function rememberLineCount(path3, lineCount) {
1492
+ let stat;
1493
+ try {
1494
+ const st = statSync(path3);
1495
+ stat = { mtimeMs: st.mtimeMs, size: st.size };
1496
+ } catch {
1497
+ return;
1498
+ }
1499
+ lineCountCache.delete(path3);
1500
+ lineCountCache.set(path3, { ...stat, lineCount });
1501
+ while (lineCountCache.size > LINE_COUNT_CACHE_MAX) {
1502
+ const oldest = lineCountCache.keys().next();
1503
+ if (oldest.done === true) break;
1504
+ lineCountCache.delete(oldest.value);
1505
+ }
1506
+ }
1507
+ var CANONICAL_RELATIONSHIP_KINDS = [
1508
+ "calls",
1509
+ "extends",
1510
+ "implements",
1511
+ "uses",
1512
+ "instantiates",
1513
+ "overrides",
1514
+ "contains",
1515
+ "references",
1516
+ "imports",
1517
+ "exports",
1518
+ "decorates",
1519
+ "renders",
1520
+ "mixes_in",
1521
+ "part_of"
1522
+ ];
1523
+ var RELATIONSHIP_KIND_ALIASES = {
1524
+ call: "calls",
1525
+ inherits: "extends",
1526
+ import: "imports",
1527
+ use: "uses",
1528
+ usage: "uses",
1529
+ reference: "references"
1530
+ };
1531
+ function canonicalKind(relationshipType) {
1532
+ const t = relationshipType.trim().toLowerCase();
1533
+ if (CANONICAL_RELATIONSHIP_KINDS.includes(t)) return t;
1534
+ return RELATIONSHIP_KIND_ALIASES[t] ?? null;
1535
+ }
1536
+ var RELATIONSHIP_VERB = {
1537
+ calls: { outgoing: "calls", incoming: "called by" },
1538
+ extends: { outgoing: "extends", incoming: "extended by" },
1539
+ implements: { outgoing: "implements", incoming: "implemented by" },
1540
+ uses: { outgoing: "uses", incoming: "used by" },
1541
+ instantiates: { outgoing: "instantiates", incoming: "instantiated by" },
1542
+ overrides: { outgoing: "overrides", incoming: "overridden by" },
1543
+ // §14 rule 4 + the owner's decision: `contains` reads as a CONTAINMENT verb,
1544
+ // never as a usage or a connection. The words are chosen so that
1545
+ // "Hub contains greet" and "greet — contained by Hub" are statements about
1546
+ // DECLARATION from either end, and so that no reader can take either for
1547
+ // "someone uses this". The negative test that proves it is in the gate.
1548
+ contains: { outgoing: "contains", incoming: "contained by" },
1549
+ references: { outgoing: "references", incoming: "referenced by" },
1550
+ imports: { outgoing: "imports", incoming: "imported by" },
1551
+ // `exports` is the other declaration containment (relations.ts). Same law.
1552
+ exports: { outgoing: "exports", incoming: "exported by" },
1553
+ // ── the four EXTENDED kinds (2026-10-18 vocabulary extension) ─────────────
1554
+ // `decorates`: the EMITTED direction is decorated declaration -> decorator, so
1555
+ // a card for the decorated declaration reads "carries decorator [X]" and a card
1556
+ // for the decorator reads "decorates [Y]". Not "called by": nothing invokes a
1557
+ // decorator at the source level (src/ast/types.ts says so in as many words).
1558
+ decorates: { outgoing: "carries decorator", incoming: "decorates" },
1559
+ // `renders`: rendering component -> rendered component (JSX element creation;
1560
+ // NOT `instantiates` — nothing is `new`ed in the source).
1561
+ renders: { outgoing: "renders", incoming: "rendered by" },
1562
+ // `mixes_in`: class -> mixin composed into its supertype chain (Dart `with`).
1563
+ // A supertype-composition relation, like `extends`/`implements`.
1564
+ mixes_in: { outgoing: "mixes in", incoming: "mixed in by" },
1565
+ // `part_of`: a `part`/`part of` DIRECTIVE. Its emitted direction is the
1566
+ // directive's own and metadata.partRole names which end is the library, so the
1567
+ // two direction words describe the DIRECTIVE, not a symbol-usage claim: a card
1568
+ // reading "part-of directive" or "named by part-of directive" is true whichever
1569
+ // role this row carries. The card never claims a part USES a library (§13.5:
1570
+ // a module-level edge is a directive, not a symbol usage). Rows whose far end
1571
+ // the index could not resolve as a symbol are declared in DECLARED_ABSENCE.
1572
+ part_of: { outgoing: "part-of directive", incoming: "named by part-of directive" }
1573
+ };
1574
+ function verbFor(relationshipType, direction) {
1575
+ const canonical = canonicalKind(relationshipType);
1576
+ if (canonical === null) return null;
1577
+ const entry = RELATIONSHIP_VERB[canonical];
1578
+ return direction === "incoming" ? entry.incoming : entry.outgoing;
1579
+ }
1580
+ var CONTAINMENT_VERB_DECLARES = "declares";
1581
+ var CONTAINMENT_VERB_DECLARED_IN = "declared in";
1582
+ var USAGE_COMMAND = "bb.openUsage";
1583
+ var DEFINITION_COMMAND = "bb.openDefinition";
1584
+ var SHOW_ALL_USAGES_COMMAND = "bb.showAllUsages";
1585
+ var OPEN_GRAPH_COMMAND = "bb.openGraph";
1586
+ var OPEN_IMPACT_COMMAND = "bb.openImpact";
1587
+ var SHOW_CALL_HIERARCHY_COMMAND = "bb.showCallHierarchy";
1588
+ function encodeLocationArgs(path3, line, character) {
1589
+ return encodeURIComponent(JSON.stringify([{ file: path3, line, character }]));
1590
+ }
1591
+ function openUsageUri(site) {
1592
+ return `command:${USAGE_COMMAND}?${encodeLocationArgs(site.path, site.line, site.character)}`;
1593
+ }
1594
+ function openDefinitionUri(path3, line, character) {
1595
+ return `command:${DEFINITION_COMMAND}?${encodeLocationArgs(path3, line, character)}`;
1596
+ }
1597
+ function encodeActionArgs(symbol, path3, line, character) {
1598
+ return encodeURIComponent(JSON.stringify([{ symbol, file: path3, line, character }]));
1599
+ }
1600
+ function openGraphUri(a) {
1601
+ return `command:${OPEN_GRAPH_COMMAND}?${encodeActionArgs(a.symbol, a.file, a.line, a.character)}`;
1602
+ }
1603
+ function openImpactUri(a) {
1604
+ return `command:${OPEN_IMPACT_COMMAND}?${encodeActionArgs(a.symbol, a.file, a.line, a.character)}`;
1605
+ }
1606
+ function showCallHierarchyUri(a) {
1607
+ return `command:${SHOW_CALL_HIERARCHY_COMMAND}?${encodeActionArgs(a.symbol, a.file, a.line, a.character)}`;
1608
+ }
1609
+ function actionsSection(symbol, filePath, line, character) {
1610
+ if (symbol.length === 0 || filePath.length === 0 || line < 0 || character < 0) return "";
1611
+ const a = { symbol, file: filePath, line, character };
1612
+ return `**actions**: [Open in graph](${openGraphUri(a)}) \xB7 [Impact analysis](${openImpactUri(a)}) \xB7 [Call hierarchy](${showCallHierarchyUri(a)})`;
1613
+ }
1614
+ function showAllUsagesUri(a) {
1615
+ return `command:${SHOW_ALL_USAGES_COMMAND}?${encodeURIComponent(JSON.stringify([{ file: a.file, line: a.line, character: a.character, symbol: a.symbol }]))}`;
1616
+ }
1617
+ function siteLabel(site) {
1618
+ const base = site.path.split(/[\\/]/).pop() ?? site.path;
1619
+ const label = `${site.name} \u2014 ${base}:${site.line + 1}`;
1620
+ return addressNoteSuffix(site.callSite) === null ? label : `${label} ${addressNoteSuffix(site.callSite)}`;
1621
+ }
1622
+ function addressNoteSuffix(address) {
1623
+ if (address === void 0 || address === null) return null;
1624
+ if (isExactAddress(address)) return null;
1625
+ return address.kind === "declaration" ? `_(${ADDRESS_NOTES.substitute})_` : `(${address.kind === "unknown" ? address.note : ""})`;
1626
+ }
1627
+ function richSitesForSymbol(raw, symbolId, omissions) {
1628
+ const locationMemo = /* @__PURE__ */ new Map();
1629
+ const edgeSourceCache = /* @__PURE__ */ new Map();
1630
+ const locationOf = (p, bs, be, ls, le) => {
1631
+ const key = `${p}:${bs}:${be}:${ls}:${le}`;
1632
+ const hit = locationMemo.get(key);
1633
+ if (hit !== void 0 || locationMemo.has(key)) return hit ?? null;
1634
+ let loc;
1635
+ try {
1636
+ loc = edgeLocation(p, bs, be, ls, le, edgeSourceCache);
1637
+ } catch (error) {
1638
+ if (!(error instanceof PositionError)) throw error;
1639
+ loc = null;
1640
+ }
1641
+ locationMemo.set(key, loc);
1642
+ return loc;
1643
+ };
1644
+ const lineCountOf = (p) => {
1645
+ const cached = edgeSourceCache.get(p);
1646
+ if (cached !== void 0) {
1647
+ if ("readError" in cached) return null;
1648
+ if (cached.mapper === void 0) cached.mapper = createPositionMapper(cached.text);
1649
+ const count = cached.mapper.lineTexts.length;
1650
+ rememberLineCount(p, count);
1651
+ return count;
1652
+ }
1653
+ const hit = cachedLineCount(p);
1654
+ if (hit !== null) return hit;
1655
+ try {
1656
+ const text = readFileSync3(p, "utf8");
1657
+ const lines = text.split("\n");
1658
+ const mapper = createPositionMapper(text);
1659
+ edgeSourceCache.set(p, { text, lines, mapper });
1660
+ rememberLineCount(p, mapper.lineTexts.length);
1661
+ return mapper.lineTexts.length;
1662
+ } catch (error) {
1663
+ edgeSourceCache.set(p, { readError: error.message });
1664
+ return null;
1665
+ }
1666
+ };
1667
+ const rows = raw.prepare(
1668
+ `SELECT r.id, r.relationship_type, r.confidence, r.metadata, r.time_updated,
1669
+ r.source_symbol_id, r.target_symbol_id, ${STORED_LINE_SELECT},
1670
+ ss.name AS src_name, ss.symbol_type AS src_type,
1671
+ ss.byte_start AS src_bs, ss.byte_end AS src_be,
1672
+ ss.line_start AS src_ls, ss.line_end AS src_le, sf.path AS src_path,
1673
+ ss.line_start AS src_decl, sf.relative_path AS src_rel,
1674
+ ts.name AS tgt_name, ts.symbol_type AS tgt_type,
1675
+ ts.byte_start AS tgt_bs, ts.byte_end AS tgt_be,
1676
+ ts.line_start AS tgt_ls, ts.line_end AS tgt_le, tf.path AS tgt_path,
1677
+ ts.line_start AS tgt_decl, tf.relative_path AS tgt_rel
1678
+ FROM brain_relationship r
1679
+ LEFT JOIN brain_symbol ss ON ss.id = r.source_symbol_id
1680
+ LEFT JOIN brain_file sf ON sf.id = ss.file_id
1681
+ LEFT JOIN brain_symbol ts ON ts.id = r.target_symbol_id
1682
+ LEFT JOIN brain_file tf ON tf.id = ts.file_id
1683
+ WHERE r.source_symbol_id = ? OR r.target_symbol_id = ?
1684
+ ORDER BY r.confidence DESC, r.id`
1685
+ ).all(symbolId, symbolId);
1686
+ const sites = [];
1687
+ const seen = /* @__PURE__ */ new Set();
1688
+ for (const row of rows) {
1689
+ if (row.source_symbol_id === symbolId && row.target_symbol_id === symbolId) continue;
1690
+ const incoming = row.target_symbol_id === symbolId && row.source_symbol_id !== symbolId;
1691
+ const other = incoming ? { name: row.src_name, type: row.src_type, path: row.src_path, bs: row.src_bs, be: row.src_be, ls: row.src_ls, le: row.src_le, decl: row.src_decl, rel: row.src_rel } : { name: row.tgt_name, type: row.tgt_type, path: row.tgt_path, bs: row.tgt_bs, be: row.tgt_be, ls: row.tgt_ls, le: row.tgt_le, decl: row.tgt_decl, rel: row.tgt_rel };
1692
+ if (!other.name || !other.path) continue;
1693
+ const sourceRel = incoming ? other.rel : row.src_rel;
1694
+ const sourceDeclLine = incoming ? other.decl : row.src_decl;
1695
+ const address = deriveCallSiteAddress({
1696
+ storedLine: storedLineFromRow(row),
1697
+ file: sourceRel ?? "",
1698
+ callerDeclaration: { line: sourceDeclLine }
1699
+ });
1700
+ const callLine0 = addressLine0(address);
1701
+ const declarationRow = isDeclarationRelationship(row.relationship_type);
1702
+ const servesCallSite = callLine0 !== null && incoming && !declarationRow;
1703
+ let loc;
1704
+ if (servesCallSite) {
1705
+ const lineCount = lineCountOf(other.path);
1706
+ if (lineCount === null || callLine0 >= lineCount) {
1707
+ if (omissions !== void 0) omissions.unresolvable++;
1708
+ continue;
1709
+ }
1710
+ loc = { path: other.path, line: callLine0, character: 0, endLine: callLine0, endCharacter: 0 };
1711
+ } else {
1712
+ if (other.bs === null || other.be === null || other.ls === null || other.le === null) continue;
1713
+ const resolved = locationOf(other.path, other.bs, other.be, other.ls, other.le);
1714
+ if (resolved === null) continue;
1715
+ loc = resolved;
1716
+ }
1717
+ const opening = servesCallSite ? { path: loc.path, line: callLine0, character: 0, endLine: callLine0, endCharacter: 0 } : { path: loc.path, line: loc.line, character: loc.character, endLine: loc.endLine, endCharacter: loc.endCharacter };
1718
+ const key = `${opening.path}:${opening.line}:${opening.character}:${incoming ? "in" : "out"}`;
1719
+ const existing = sites.find((x) => `${x.path}:${x.line}:${x.character}:${x.reversed ? "in" : "out"}` === key);
1720
+ if (existing !== void 0) {
1721
+ if (!existing.relationshipTypes.includes(row.relationship_type)) {
1722
+ existing.relationshipTypes.push(row.relationship_type);
1723
+ }
1724
+ continue;
1725
+ }
1726
+ seen.add(key);
1727
+ const enrichment = parseEnrichment(row.metadata);
1728
+ sites.push({
1729
+ name: other.name,
1730
+ symbolType: other.type,
1731
+ path: opening.path,
1732
+ line: opening.line,
1733
+ character: opening.character,
1734
+ endLine: opening.endLine,
1735
+ endCharacter: opening.endCharacter,
1736
+ relationshipType: row.relationship_type,
1737
+ relationshipTypes: [row.relationship_type],
1738
+ reversed: incoming,
1739
+ confidence: row.confidence,
1740
+ enrichment,
1741
+ enrichmentStale: enrichment === null ? false : enrichmentIsStale(enrichment, row.time_updated),
1742
+ callSite: address
1743
+ });
1744
+ }
1745
+ return sites;
1746
+ }
1747
+ function connectedGroups(sites) {
1748
+ const groups = /* @__PURE__ */ new Map();
1749
+ for (const site of sites) {
1750
+ for (const kind of siteKinds(site)) {
1751
+ if (isDeclarationRelationship(kind)) continue;
1752
+ const verb = verbFor(kind, site.reversed ? "incoming" : "outgoing");
1753
+ if (verb === null) continue;
1754
+ let g = groups.get(verb);
1755
+ if (!g) {
1756
+ g = { verb, sites: [] };
1757
+ groups.set(verb, g);
1758
+ }
1759
+ g.sites.push(site);
1760
+ }
1761
+ }
1762
+ return [...groups.values()];
1763
+ }
1764
+ function siteKinds(site) {
1765
+ const kinds = site.relationshipTypes;
1766
+ return kinds === void 0 || kinds.length === 0 ? [site.relationshipType] : [...kinds];
1767
+ }
1768
+ var MAX_SUMMARY_PER_VERB = 3;
1769
+ function connectedSummarySection(groups) {
1770
+ if (groups.length === 0) return "";
1771
+ const parts = [];
1772
+ for (const group of groups) {
1773
+ const names = group.sites.slice(0, MAX_SUMMARY_PER_VERB);
1774
+ const overflow = group.sites.length - names.length;
1775
+ const seen = /* @__PURE__ */ new Set();
1776
+ const links = names.map((s) => {
1777
+ const dup = seen.has(s.name);
1778
+ seen.add(s.name);
1779
+ const label = dup ? siteLabel(s) : s.name;
1780
+ return `[${label}](${openUsageUri(s)})`;
1781
+ });
1782
+ if (overflow > 0) links.push(`+${overflow}`);
1783
+ let groupText = `${group.verb} ${links.join(", ")}`;
1784
+ if (group.sites.length === 1 && hasDisplayableEnrichment(group.sites[0].enrichment)) {
1785
+ const s = group.sites[0];
1786
+ const desc = describeEnrichment(s.enrichment);
1787
+ const staleNote = s.enrichmentStale ? ", stale \u2014 row changed after enrichment" : "";
1788
+ groupText += ` \xB7 _${desc} (enriched by index${staleNote})_`;
1789
+ }
1790
+ parts.push(groupText);
1791
+ }
1792
+ return `**connected**: ${parts.join("; ")}`;
1793
+ }
1794
+ function usageSitesOf(sites) {
1795
+ return usageSites(sites);
1796
+ }
1797
+ function usagesSection(sites, total, forSymbol, forFile, forLine, forCharacter) {
1798
+ const usage = usageSitesOf(sites);
1799
+ if (usage.length === 0) return "";
1800
+ const lines = [`**usages** \u2014 ${usage.length} stored reference${usage.length === 1 ? "" : "s"} (${USAGE_SITE_FILTER_LABEL})`];
1801
+ const exact = usage.filter((s) => isExactAddress(s.callSite)).length;
1802
+ lines[0] += exact === usage.length ? " \u2014 each opens the stored call site" : exact === 0 ? ` \u2014 the index stores no call-site line for these, so each opens the referencing declaration` : ` \u2014 ${exact} open the stored call site, ${usage.length - exact} open the referencing declaration (no call-site line stored)`;
1803
+ lines[0] += ` _(${USAGE_COUNT_UNIT_LABEL})_`;
1804
+ const shown = usage.slice(0, MAX_USAGE_LINKS);
1805
+ shown.forEach((s, i) => {
1806
+ lines.push(`${i + 1}. [${siteLabel(s)}](${openUsageUri(s)})`);
1807
+ });
1808
+ const rest = Math.max(0, Math.min(total, usage.length) - shown.length);
1809
+ if (rest > 0) {
1810
+ lines.push(`+${rest} more \u2014 [show all usages](${showAllUsagesUri({ file: forFile, line: forLine, character: forCharacter, symbol: forSymbol })})`);
1811
+ }
1812
+ return lines.join("\n");
1813
+ }
1814
+ var MAX_CONTAINMENT_ROWS = 6;
1815
+ function containmentSitesOf(sites) {
1816
+ return sites.filter((s) => siteKinds(s).some((k) => isDeclarationRelationship(k)));
1817
+ }
1818
+ function containmentSection(sites) {
1819
+ const declared = containmentSitesOf(sites);
1820
+ if (declared.length === 0) return "";
1821
+ const members = declared.filter((s) => !s.reversed);
1822
+ const owners = declared.filter((s) => s.reversed);
1823
+ const parts = [];
1824
+ if (members.length > 0) parts.push(`${CONTAINMENT_VERB_DECLARES} ${linkedList(members)}`);
1825
+ if (owners.length > 0) parts.push(`${CONTAINMENT_VERB_DECLARED_IN} ${linkedList(owners)}`);
1826
+ const multi = declared.map((s) => ({ site: s, kinds: siteKinds(s).filter((k) => isDeclarationRelationship(k)) })).filter((x) => x.kinds.length > 1);
1827
+ for (const x of multi) parts.push(`_(also declared by ${x.kinds.join(", ")})_`);
1828
+ const shown = Math.min(declared.length, MAX_CONTAINMENT_ROWS);
1829
+ const rest = declared.length - shown;
1830
+ const header = `**contained** \u2014 declares ${members.length} member${members.length === 1 ? "" : "s"}, declared in ${owners.length} (declaration containment; never a usage)` + (rest > 0 ? ` \u2014 showing ${shown} of ${declared.length}, +${rest} more on the symbol's own list` : "");
1831
+ return [header, ...parts].join("\n");
1832
+ }
1833
+ function linkedList(sites) {
1834
+ const seen = /* @__PURE__ */ new Set();
1835
+ const links = sites.map((s) => {
1836
+ const dup = seen.has(s.name);
1837
+ seen.add(s.name);
1838
+ const label = dup ? siteLabel(s) : s.name;
1839
+ return `[${label}](${openUsageUri(s)})`;
1840
+ });
1841
+ return links.join(", ");
1842
+ }
1843
+ function definitionLink(path3, line, character) {
1844
+ return `_source: bb index ([${path3}:${line + 1}](${openDefinitionUri(path3, line, character)}))_`;
1845
+ }
1846
+
1847
+ // src/lsp/hover.ts
1848
+ var SYMBOL_ROW_SELECT = `s.id, s.name, s.qualified_name, s.symbol_type, s.line_start, s.line_end,
1849
+ s.byte_start, s.byte_end, s.return_type, s.parameters, s.visibility, s.file_id,
1850
+ f.id AS f_id, f.path, f.relative_path, f.language`;
1851
+ function findSymbolAtPosition(raw, filePath, position, mapper) {
1852
+ let line1;
1853
+ if (mapper !== void 0) {
1854
+ try {
1855
+ line1 = mapper.lspToIndex(position).line;
1856
+ } catch (error) {
1857
+ if (error instanceof PositionError) return null;
1858
+ throw error;
1859
+ }
1860
+ } else {
1861
+ let content;
1862
+ try {
1863
+ content = readFileSync4(filePath, "utf8");
1864
+ } catch {
1865
+ return null;
1866
+ }
1867
+ try {
1868
+ line1 = createPositionMapper(content).lspToIndex(position).line;
1869
+ } catch (error) {
1870
+ if (error instanceof PositionError) return null;
1871
+ throw error;
1872
+ }
1873
+ }
1874
+ const rows = raw.prepare(
1875
+ `SELECT ${SYMBOL_ROW_SELECT}
1876
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
1877
+ WHERE f.path = ? AND s.line_start IS NOT NULL AND s.line_end IS NOT NULL
1878
+ AND s.line_start <= ? AND s.line_end >= ?`
1879
+ ).all(filePath, line1, line1);
1880
+ if (rows.length === 0) return null;
1881
+ let best = null;
1882
+ let bestWidth = Number.MAX_SAFE_INTEGER;
1883
+ for (const row of rows) {
1884
+ const width = (row.line_end ?? 0) - (row.line_start ?? 0);
1885
+ if (width < bestWidth) {
1886
+ bestWidth = width;
1887
+ best = {
1888
+ symbol: row,
1889
+ file: { id: row.f_id, path: row.path, relative_path: row.relative_path ?? "", language: row.language }
1890
+ };
1891
+ }
1892
+ }
1893
+ return best;
1894
+ }
1895
+ function indexedFileCountFor(raw, fileId) {
1896
+ if (fileId === null || fileId === void 0 || fileId === "") return null;
1897
+ try {
1898
+ const row = raw.prepare("SELECT count(*) AS c FROM brain_file WHERE project_id = (SELECT project_id FROM brain_file WHERE id = ?)").get(fileId);
1899
+ const n = Number(row?.c);
1900
+ return Number.isFinite(n) ? n : null;
1901
+ } catch {
1902
+ return null;
1903
+ }
1904
+ }
1905
+ async function resolveOccurrenceSymbol(raw, filePath, position) {
1906
+ let content;
1907
+ try {
1908
+ content = readFileSync4(filePath, "utf8");
1909
+ } catch {
1910
+ return null;
1911
+ }
1912
+ const mapper = createPositionMapper(content);
1913
+ let indexPos;
1914
+ try {
1915
+ indexPos = mapper.lspToIndex({ line: position.line, character: position.character });
1916
+ } catch (e) {
1917
+ if (e instanceof PositionError) return null;
1918
+ throw e;
1919
+ }
1920
+ let parsed;
1921
+ try {
1922
+ parsed = await parseForCursor(filePath, content);
1923
+ } catch (error) {
1924
+ if (error instanceof ParseUnavailable) return null;
1925
+ throw error;
1926
+ }
1927
+ const cursorOcc = occurrenceAtPoint(parsed.tree, indexPos.line, indexPos.column);
1928
+ if (!cursorOcc) return null;
1929
+ const sameFile = raw.prepare(`SELECT ${SYMBOL_ROW_SELECT} FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id WHERE f.path = ? AND s.name = ?`).all(filePath, cursorOcc.text);
1930
+ let rows = sameFile;
1931
+ if (sameFile.length === 0) {
1932
+ const workspace = raw.prepare(`SELECT ${SYMBOL_ROW_SELECT} FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id WHERE s.name = ?`).all(cursorOcc.text);
1933
+ rows = workspace;
1934
+ }
1935
+ if (rows.length !== 1) return null;
1936
+ const row = rows[0];
1937
+ return {
1938
+ symbol: row,
1939
+ file: { id: row.f_id, path: row.path, relative_path: row.relative_path ?? "", language: row.language },
1940
+ occurrence: { startLine: cursorOcc.startLine, startColumn: cursorOcc.startColumn, endColumn: cursorOcc.endColumn },
1941
+ // Lane 6: the mapper for THIS file's text, handed back so the containing-
1942
+ // symbol fallback can convert its line through the same owner without a
1943
+ // second read. Two P1 mappers over the same bytes would be two answers to
1944
+ // the same question, which is the drift §5.1 exists to prevent.
1945
+ mapper,
1946
+ // the declaration's own documentation, read from the comment sites
1947
+ // this parse already produced. Attached to the DECLARATION, not to the
1948
+ // cursor: a doc describes the symbol, and the cursor is often a use of it.
1949
+ // Same rule as doc_comment.ts (a comment ending on the line directly above), read from the point that
1950
+ // can satisfy it instead of a whole-file comment walk — equivalence gated line-by-line.
1951
+ docComment: docCommentAt(parsed.tree, row.line_start)
1952
+ };
1953
+ }
1954
+ function evidenceTiersFor(raw, symbolId) {
1955
+ const rows = raw.prepare(
1956
+ "SELECT relationship_type, confidence, metadata FROM brain_relationship WHERE target_symbol_id = ? UNION SELECT relationship_type, confidence, metadata FROM brain_relationship WHERE source_symbol_id = ?"
1957
+ ).all(symbolId, symbolId);
1958
+ return orderTiers(rows.map(classifyStoredRow));
1959
+ }
1960
+ function formatHoverMarkdown(data) {
1961
+ const lines = [];
1962
+ lines.push(`\`\`\`
1963
+ ${data.signature}
1964
+ \`\`\``);
1965
+ if (data.docComment.length > 0) {
1966
+ lines.push(data.docComment);
1967
+ lines.push("");
1968
+ }
1969
+ lines.push(renderTypeLine(data.typeLine));
1970
+ lines.push(
1971
+ `**${data.name}** \u2014 ${data.symbolType}, ${data.usageCount} usage${data.usageCount === 1 ? "" : "s"}${data.indexedFileCount === null ? " (indexed scope unknown \u2014 this count is bounded by something not measured)" : ` in ${data.indexedFileCount} indexed file${data.indexedFileCount === 1 ? "" : "s"}`} _(${USAGE_SITE_FILTER_LABEL})_`
1972
+ );
1973
+ if (data.evidenceTiers.length > 0) {
1974
+ lines.push(`evidence: ${data.evidenceTiers.join(", ")}`);
1975
+ } else {
1976
+ lines.push("evidence: none (no indexed relationships)");
1977
+ }
1978
+ if (data.connectedSection && data.connectedSection.length > 0) {
1979
+ lines.push(data.connectedSection);
1980
+ }
1981
+ if (data.usagesSection && data.usagesSection.length > 0) {
1982
+ lines.push(data.usagesSection);
1983
+ }
1984
+ if (data.omittedUsageRows.unresolvable > 0) {
1985
+ const n = data.omittedUsageRows.unresolvable;
1986
+ lines.push(
1987
+ `**usages omitted** \u2014 ${n} stored row${n === 1 ? "" : "s"} name a call-site line past the end of the source file, so ${n === 1 ? "it is" : "they are"} not served (\xA715.3: omitted and counted, never clamped); \`textDocument/references\` omits the same ${n === 1 ? "row" : "rows"}.`
1988
+ );
1989
+ }
1990
+ if (data.unresolvedNameMatches && data.unresolvedNameMatches.total > 0) {
1991
+ const m = data.unresolvedNameMatches;
1992
+ lines.push(
1993
+ `**name matches without identity** \u2014 ${m.total} stored row${m.total === 1 ? "" : "s"} name \`${m.name}\` but resolve to no indexed declaration (${m.external} classified external, ${m.unresolved} unresolved); these are not counted as usages.`
1994
+ );
1995
+ }
1996
+ if (data.containmentSection && data.containmentSection.length > 0) {
1997
+ lines.push(data.containmentSection);
1998
+ }
1999
+ if (data.actionsSection && data.actionsSection.length > 0) {
2000
+ lines.push(data.actionsSection);
2001
+ }
2002
+ if (data.crossLanguageSection && data.crossLanguageSection.length > 0) {
2003
+ lines.push(data.crossLanguageSection);
2004
+ }
2005
+ if (data.annotationSection && data.annotationSection.length > 0) {
2006
+ lines.push(data.annotationSection);
2007
+ }
2008
+ if (data.stale) {
2009
+ lines.push(`\u26A0\uFE0F **stale** \u2014 file changed since indexing (${data.freshnessStatus})`);
2010
+ }
2011
+ if (data.definition !== null) {
2012
+ lines.push(definitionLink(data.filePath, data.definition.line, data.definition.character));
2013
+ } else {
2014
+ lines.push(`_source: bb index (${data.filePath}:${data.lineStart ?? "?"})_`);
2015
+ }
2016
+ return lines.join("\n\n");
2017
+ }
2018
+ async function hover(raw, request, deps = {}) {
2019
+ const startedAt = (deps.now ?? Date.now)();
2020
+ const language = languageForPath(request.filePath);
2021
+ if (!isLaunchLanguage(language)) {
2022
+ return {
2023
+ contents: null,
2024
+ range: null,
2025
+ data: null,
2026
+ provenance: {
2027
+ source: "bb_index",
2028
+ evidenceClass: "unknown",
2029
+ engine: "bb_lsp hover@m24.l2",
2030
+ limitations: [
2031
+ language === null ? "file type not in the launch language set" : `${language} is not a launch language (launch: ${LAUNCH_LANGUAGES.join(", ")})`
2032
+ ],
2033
+ stale: false
2034
+ }
2035
+ };
2036
+ }
2037
+ const occurrence = await resolveOccurrenceSymbol(raw, request.filePath, request.position);
2038
+ const containing = findSymbolAtPosition(raw, request.filePath, request.position, occurrence?.mapper);
2039
+ const found = occurrence ?? containing;
2040
+ if (!found) {
2041
+ return {
2042
+ contents: null,
2043
+ range: null,
2044
+ data: null,
2045
+ provenance: {
2046
+ source: "bb_index",
2047
+ evidenceClass: "unknown",
2048
+ engine: "bb_lsp hover@m24.l2",
2049
+ limitations: ["no indexed symbol at this position (not indexed)"],
2050
+ stale: false
2051
+ }
2052
+ };
2053
+ }
2054
+ const { symbol, file } = found;
2055
+ const freshness = checkFileFreshness(raw, file.id);
2056
+ const freshnessStatus = freshness?.status ?? "missing";
2057
+ const stale = freshnessStatus !== "fresh";
2058
+ const tiers = evidenceTiersFor(raw, symbol.id);
2059
+ const omittedUsageRows = { unresolvable: 0 };
2060
+ const sites = richSitesForSymbol(raw, symbol.id, omittedUsageRows);
2061
+ const usageSiteRows = usageSitesOf(sites);
2062
+ const usageCount = usageSiteRows.length;
2063
+ const unresolvedNameMatches = countUnresolvedNameMatches(raw, symbol.id);
2064
+ const indexedFileCount = indexedFileCountFor(raw, file.id);
2065
+ const crossSection = crossLanguageSection(factsFromRows(storedCrossLanguageRows(raw, symbol.id), file.language));
2066
+ const annotationRows = raw.prepare(
2067
+ "SELECT symbol_name, symbol_path, author_class, tool_identity, body, time_created, time_updated FROM brain_annotation WHERE project_id = (SELECT project_id FROM brain_file WHERE id = ?) AND (symbol_name = ? OR symbol_name = ?) AND (symbol_path IS NULL OR symbol_path = ? OR symbol_path = ?) ORDER BY time_updated DESC, id"
2068
+ ).all(file.id, symbol.name, symbol.qualified_name, file.relative_path, file.path);
2069
+ const annotationSection = annotationHoverSection(annotationRows);
2070
+ let range = null;
2071
+ if (symbol.line_start !== null && symbol.line_end !== null && symbol.byte_start !== null && symbol.byte_end !== null) {
2072
+ try {
2073
+ const sourceText = readFileSync4(file.path, "utf8");
2074
+ const startCol = resolveRowByteColumns(sourceText, symbol.line_start, symbol.byte_start);
2075
+ const endCol = resolveRowByteColumns(sourceText, symbol.line_end, symbol.byte_end);
2076
+ if (startCol === null || endCol === null) {
2077
+ } else {
2078
+ const mapper = createPositionMapper(sourceText);
2079
+ const indexRange = {
2080
+ start: { line: startCol.line, column: startCol.column },
2081
+ end: { line: endCol.line, column: endCol.column }
2082
+ };
2083
+ range = mapper.indexRangeToLsp(indexRange);
2084
+ }
2085
+ } catch (error) {
2086
+ if (!(error instanceof PositionError)) throw error;
2087
+ }
2088
+ }
2089
+ const jumped = occurrence !== null && (containing === null || containing.symbol.id !== symbol.id);
2090
+ let responseRange = range;
2091
+ if (jumped) {
2092
+ try {
2093
+ const sourceText = readFileSync4(request.filePath, "utf8");
2094
+ const startCol = resolveRowByteColumns(sourceText, occurrence.occurrence.startLine, occurrence.occurrence.startColumn);
2095
+ const endCol = resolveRowByteColumns(sourceText, occurrence.occurrence.startLine, occurrence.occurrence.endColumn);
2096
+ if (startCol !== null && endCol !== null) {
2097
+ const cursorMapper = createPositionMapper(sourceText);
2098
+ responseRange = cursorMapper.indexRangeToLsp({
2099
+ start: { line: startCol.line, column: startCol.column },
2100
+ end: { line: endCol.line, column: endCol.column }
2101
+ });
2102
+ }
2103
+ } catch (error) {
2104
+ if (!(error instanceof PositionError)) throw error;
2105
+ responseRange = null;
2106
+ }
2107
+ }
2108
+ let typeLine;
2109
+ try {
2110
+ const typeSource = readFileSync4(file.path, "utf8");
2111
+ typeLine = (await resolveTypeLine(
2112
+ file.path,
2113
+ typeSource,
2114
+ {
2115
+ name: symbol.name,
2116
+ symbol_type: symbol.symbol_type,
2117
+ return_type: symbol.return_type,
2118
+ line_start: symbol.line_start,
2119
+ line_end: symbol.line_end,
2120
+ language: file.language
2121
+ },
2122
+ symbol.return_type
2123
+ )).line;
2124
+ typeLine.stale = stale;
2125
+ } catch {
2126
+ typeLine = {
2127
+ provenance: "unknown",
2128
+ text: UNRESOLVED_TYPE,
2129
+ confidence: null,
2130
+ reason: "the type could not be resolved; the hover renders the declared marker",
2131
+ withheld: "parse-failed",
2132
+ withheldText: null,
2133
+ storedProvenanceUnknown: false,
2134
+ stale,
2135
+ witness: null
2136
+ };
2137
+ }
2138
+ const data = {
2139
+ name: symbol.name,
2140
+ qualifiedName: symbol.qualified_name,
2141
+ symbolType: symbol.symbol_type,
2142
+ signature: renderSignature(symbol, file.language),
2143
+ // structured data stays structured (see the field's declaration).
2144
+ parameters: (() => {
2145
+ const parsed = parseParameters(symbol.parameters);
2146
+ if (parsed.shape === "empty") return [];
2147
+ if (parsed.shape !== "list") return null;
2148
+ return parsed.entries.map((e) => ({
2149
+ name: e.name,
2150
+ type: e.type ?? null,
2151
+ isOptional: e.isOptional === true,
2152
+ defaultValue: e.defaultValue ?? null,
2153
+ isRest: e.isRest === true
2154
+ }));
2155
+ })(),
2156
+ usageCount,
2157
+ unresolvedNameMatches,
2158
+ omittedUsageRows,
2159
+ indexedFileCount,
2160
+ evidenceTiers: tiers,
2161
+ // rich sections composed from stored relationship rows.
2162
+ connectedSection: connectedSummarySection(connectedGroups(sites)),
2163
+ usagesSection: usagesSection(usageSiteRows, usageCount, symbol.name, file.path, request.position.line, request.position.character),
2164
+ // The declaration-containment rows come from the SAME collected array the
2165
+ // usage list and the connected summary read (one collection, three
2166
+ // renderings), so a containment count can never disagree with a usage count
2167
+ // about the same rows.
2168
+ containmentSection: containmentSection(sites),
2169
+ // LSP definition location (range.start) when resolvable; null otherwise
2170
+ // (the footnote then renders the plain location — still honest).
2171
+ definition: range === null ? null : { line: range.start.line, character: range.start.character },
2172
+ // the actions row targets the same resolved declaration (the
2173
+ // actions carry the server-resolved symbol identity — ).
2174
+ actionsSection: range === null ? "" : actionsSection(symbol.name, file.path, range.start.line, range.start.character),
2175
+ crossLanguageSection: crossSection,
2176
+ annotationSection,
2177
+ stale,
2178
+ freshnessStatus,
2179
+ filePath: file.path,
2180
+ lineStart: symbol.line_start,
2181
+ lineEnd: symbol.line_end,
2182
+ typeLine,
2183
+ docComment: occurrence?.docComment ?? ""
2184
+ };
2185
+ const elapsed = (deps.now ?? Date.now)() - startedAt;
2186
+ void elapsed;
2187
+ return {
2188
+ contents: { value: formatHoverMarkdown(data), kind: "markdown" },
2189
+ range: responseRange,
2190
+ data,
2191
+ provenance: {
2192
+ source: "bb_index",
2193
+ // the primary is the STRONGEST tier the symbol carries, by the
2194
+ // declared rank — a member of `tiers` by construction, so the primary
2195
+ // can never disagree with the tier list it headlines.
2196
+ evidenceClass: stale ? "stale" : primaryTier(tiers),
2197
+ engine: "bb_lsp hover@m24.l2",
2198
+ limitations: [
2199
+ "index-sourced; no compiler-backed types (complement stance)"
2200
+ ],
2201
+ stale
2202
+ }
2203
+ };
2204
+ }
2205
+
2206
+ // src/lsp/document_highlight.ts
2207
+ var SAFETY_CACHE_LIMIT = 8;
2208
+ var safetyCache = /* @__PURE__ */ new Map();
2209
+ function cachedSafety(filePath, content) {
2210
+ const hit = safetyCache.get(filePath);
2211
+ if (!hit || hit.content !== content) return null;
2212
+ safetyCache.delete(filePath);
2213
+ safetyCache.set(filePath, hit);
2214
+ return hit.safety;
2215
+ }
2216
+ function rememberSafety(filePath, content, safety) {
2217
+ safetyCache.delete(filePath);
2218
+ safetyCache.set(filePath, { content, safety });
2219
+ while (safetyCache.size > SAFETY_CACHE_LIMIT) {
2220
+ const oldest = safetyCache.keys().next().value;
2221
+ if (oldest === void 0) break;
2222
+ safetyCache.delete(oldest);
2223
+ }
2224
+ }
2225
+ async function documentHighlight(raw, request) {
2226
+ let content;
2227
+ try {
2228
+ content = readFileSync5(request.filePath, "utf8");
2229
+ } catch {
2230
+ return { highlights: [], evidenceTier: "not_indexed", staleness: null, symbol: null };
2231
+ }
2232
+ const mapper = new PositionMapper(content);
2233
+ const absence = (tier) => ({
2234
+ highlights: [],
2235
+ evidenceTier: tier,
2236
+ staleness: null,
2237
+ symbol: null
2238
+ });
2239
+ let safety = cachedSafety(request.filePath, content);
2240
+ if (safety === null) {
2241
+ try {
2242
+ const parsed = await parseForSafety(request.filePath, content);
2243
+ rememberSafety(request.filePath, content, parsed);
2244
+ safety = parsed;
2245
+ } catch {
2246
+ return absence("unsupported_language");
2247
+ }
2248
+ }
2249
+ let indexPos;
2250
+ try {
2251
+ indexPos = mapper.lspToIndex({ line: request.line, character: request.character });
2252
+ } catch (e) {
2253
+ if (e instanceof PositionError) return absence("not_indexed");
2254
+ throw e;
2255
+ }
2256
+ const cursorOcc = safety.occurrences.find(
2257
+ (o) => o.startLine === indexPos.line && o.startColumn <= indexPos.column && indexPos.column < o.endColumn
2258
+ );
2259
+ const DECL_COLS = `s.id, s.name, s.line_start, s.line_end, s.byte_start, s.byte_end, s.file_id`;
2260
+ if (!cursorOcc) return absence("not_indexed");
2261
+ const sameFile = raw.prepare(
2262
+ `SELECT ${DECL_COLS} FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
2263
+ WHERE f.path = ? AND s.name = ?`
2264
+ ).all(request.filePath, cursorOcc.text);
2265
+ let symbol = sameFile.length === 1 ? sameFile[0] : null;
2266
+ if (symbol === null && sameFile.length === 0) {
2267
+ const workspace = raw.prepare(
2268
+ `SELECT ${DECL_COLS} FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
2269
+ WHERE s.name = ?`
2270
+ ).all(cursorOcc.text);
2271
+ if (workspace.length === 1) symbol = workspace[0];
2272
+ }
2273
+ if (symbol === null) {
2274
+ return absence(sameFile.length > 1 ? "ambiguous" : "not_indexed");
2275
+ }
2276
+ const edges = raw.prepare(`SELECT source_symbol_id, target_symbol_id FROM brain_relationship
2277
+ WHERE target_symbol_id = ? OR source_symbol_id = ?`).all(symbol.id, symbol.id);
2278
+ const related = /* @__PURE__ */ new Set([symbol.id]);
2279
+ for (const e of edges) {
2280
+ if (e.target_symbol_id === symbol.id) related.add(e.source_symbol_id);
2281
+ else related.add(e.target_symbol_id);
2282
+ }
2283
+ const fileSymbols = raw.prepare(`SELECT ${DECL_COLS}
2284
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
2285
+ WHERE f.path = ? AND s.line_start IS NOT NULL AND s.line_end IS NOT NULL`).all(request.filePath);
2286
+ const highlights = [];
2287
+ for (const occ of safety.occurrences) {
2288
+ if (occ.text !== symbol.name) continue;
2289
+ const occLine = occ.startLine;
2290
+ let inner = null;
2291
+ let innerSpan = Number.MAX_SAFE_INTEGER;
2292
+ for (const s of fileSymbols) {
2293
+ if (s.line_start === null || s.line_end === null) continue;
2294
+ if (occLine < s.line_start || occLine > s.line_end) continue;
2295
+ const span = s.line_end - s.line_start;
2296
+ if (span < innerSpan) {
2297
+ inner = s;
2298
+ innerSpan = span;
2299
+ }
2300
+ }
2301
+ if (inner === null || !related.has(inner.id)) continue;
2302
+ const start = resolveRowByteColumns(content, occ.startLine, occ.startColumn);
2303
+ const end = resolveRowByteColumns(content, occ.endLine, occ.endColumn);
2304
+ if (start === null || end === null) continue;
2305
+ const range = mapper.indexRangeToLsp({
2306
+ start: { line: start.line, column: start.column },
2307
+ end: { line: end.line, column: end.column }
2308
+ });
2309
+ highlights.push({
2310
+ start: range.start,
2311
+ end: range.end,
2312
+ kind: inner.id === symbol.id ? 3 : 2
2313
+ });
2314
+ }
2315
+ highlights.sort((a, b) => a.start.line - b.start.line || a.start.character - b.start.character);
2316
+ const tiers = evidenceTiersFor(raw, symbol.id);
2317
+ const freshness = checkFileFreshness(raw, symbol.file_id);
2318
+ const freshnessStatus = freshness?.status ?? "missing";
2319
+ return {
2320
+ highlights,
2321
+ evidenceTier: tiers[0] ?? "unknown",
2322
+ staleness: { stale: freshnessStatus !== "fresh", freshnessStatus },
2323
+ symbol: { id: symbol.id, name: symbol.name }
2324
+ };
2325
+ }
2326
+
2327
+ // src/lsp/server.ts
2328
+ import { fileURLToPath } from "url";
2329
+
2330
+ // src/lsp/symbols.ts
2331
+ import { readFileSync as readFileSync6 } from "fs";
2332
+ var MAX_WORKSPACE_SYMBOLS = 200;
2333
+ function declaredAbsenceFor(filePath) {
2334
+ const language = languageForPath(filePath);
2335
+ if (language === null) {
2336
+ return {
2337
+ code: "file_type_unknown",
2338
+ message: `the file's extension maps to no language in the launch map, so bb declares no symbols for it (launch: ${LAUNCH_LANGUAGES.join(", ")})`,
2339
+ language: null,
2340
+ launchLanguages: LAUNCH_LANGUAGES
2341
+ };
2342
+ }
2343
+ if (!isLaunchLanguage(language)) {
2344
+ return {
2345
+ code: "language_outside_launch_set",
2346
+ message: `${language} is not a launch language, so bb declares no symbols for it (launch: ${LAUNCH_LANGUAGES.join(", ")})`,
2347
+ language,
2348
+ launchLanguages: LAUNCH_LANGUAGES
2349
+ };
2350
+ }
2351
+ return null;
2352
+ }
2353
+ function absenceWith(code, message, filePath = null) {
2354
+ return {
2355
+ code,
2356
+ message,
2357
+ language: filePath === null ? null : languageForPath(filePath),
2358
+ launchLanguages: LAUNCH_LANGUAGES
2359
+ };
2360
+ }
2361
+ function markerFor(raw, rows) {
2362
+ const seen = /* @__PURE__ */ new Map();
2363
+ for (const row of rows) {
2364
+ if (!seen.has(row.file_id)) seen.set(row.file_id, row.path);
2365
+ }
2366
+ let fresh = 0;
2367
+ let stale = 0;
2368
+ let missing = 0;
2369
+ const filesUnverified = [];
2370
+ for (const [fileId, path3] of seen) {
2371
+ const status = checkFileFreshness(raw, fileId);
2372
+ if (status === null) {
2373
+ filesUnverified.push(path3);
2374
+ } else if (status.status === "fresh") {
2375
+ fresh++;
2376
+ } else if (status.status === "stale") {
2377
+ stale++;
2378
+ } else {
2379
+ missing++;
2380
+ }
2381
+ }
2382
+ return {
2383
+ stale: stale > 0 || missing > 0 || filesUnverified.length > 0,
2384
+ status: missing > 0 ? "missing" : stale > 0 ? "stale" : filesUnverified.length > 0 ? "unknown" : fresh > 0 ? "fresh" : "unknown",
2385
+ filesChecked: seen.size - filesUnverified.length,
2386
+ filesUnverified
2387
+ };
2388
+ }
2389
+ var WORD_CHAR = /[A-Za-z0-9_$]/;
2390
+ function isWordChar(ch) {
2391
+ return ch !== void 0 && WORD_CHAR.test(ch);
2392
+ }
2393
+ function identifierSpanAt(mapper, start, name) {
2394
+ if (name.length === 0) return null;
2395
+ const text = mapper.lineTexts[start.line];
2396
+ if (text === void 0) return null;
2397
+ const probed = text.slice(start.character, start.character + name.length);
2398
+ if (probed !== name) return null;
2399
+ if (isWordChar(text[start.character - 1])) return null;
2400
+ if (isWordChar(text[start.character + name.length])) return null;
2401
+ return {
2402
+ line: start.line,
2403
+ character: start.character,
2404
+ endLine: start.line,
2405
+ endCharacter: start.character + name.length
2406
+ };
2407
+ }
2408
+ function symbolRanges(sourceText, row) {
2409
+ if (row.byte_start === null || row.byte_end === null || row.line_start === null || row.line_end === null) {
2410
+ return null;
2411
+ }
2412
+ const startCol = resolveRowByteColumns(sourceText, row.line_start, row.byte_start);
2413
+ const endCol = resolveRowByteColumns(sourceText, row.line_end, row.byte_end);
2414
+ if (startCol === null || endCol === null) return null;
2415
+ const mapper = createPositionMapper(sourceText);
2416
+ const selection = mapper.indexRangeToLsp({
2417
+ start: { line: startCol.line, column: startCol.column },
2418
+ end: { line: endCol.line, column: endCol.column }
2419
+ });
2420
+ const range = {
2421
+ line: selection.start.line,
2422
+ character: selection.start.character,
2423
+ endLine: selection.end.line,
2424
+ endCharacter: selection.end.character
2425
+ };
2426
+ const identifier = identifierSpanAt(mapper, selection.start, row.name);
2427
+ return {
2428
+ range,
2429
+ selectionRange: identifier ?? range,
2430
+ selectionRangeBasis: identifier === null ? "declaration_span" : "identifier_at_stored_start"
2431
+ };
2432
+ }
2433
+ function documentSymbols(raw, filePath) {
2434
+ const scope = declaredAbsenceFor(filePath);
2435
+ if (scope !== null) {
2436
+ return {
2437
+ symbols: [],
2438
+ evidenceTier: "not_indexed",
2439
+ notIndexed: true,
2440
+ absence: scope,
2441
+ omitted: [],
2442
+ staleness: {
2443
+ stale: true,
2444
+ status: "unknown",
2445
+ filesChecked: 0,
2446
+ filesUnverified: [filePath]
2447
+ },
2448
+ selectionRangeBasis: "declaration_span"
2449
+ };
2450
+ }
2451
+ const rows = raw.prepare(
2452
+ `SELECT s.id, s.name, s.qualified_name, s.symbol_type, s.owner_symbol_id,
2453
+ s.file_id, s.byte_start, s.byte_end, s.line_start, s.line_end,
2454
+ s.gravity_score, f.path, f.language
2455
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
2456
+ WHERE f.path = ?
2457
+ ORDER BY s.line_start, s.byte_start, s.id`
2458
+ ).all(filePath);
2459
+ const staleness = markerFor(raw, rows);
2460
+ if (rows.length === 0) {
2461
+ return {
2462
+ symbols: [],
2463
+ evidenceTier: "not_indexed",
2464
+ notIndexed: true,
2465
+ absence: absenceWith(
2466
+ "not_in_index",
2467
+ "the index stores no symbol rows for this file (bb has no declaration to show)",
2468
+ filePath
2469
+ ),
2470
+ omitted: [],
2471
+ staleness,
2472
+ selectionRangeBasis: "declaration_span"
2473
+ };
2474
+ }
2475
+ let sourceText = null;
2476
+ try {
2477
+ sourceText = readFileSync6(filePath, "utf8");
2478
+ } catch {
2479
+ return {
2480
+ symbols: [],
2481
+ evidenceTier: "not_indexed",
2482
+ notIndexed: true,
2483
+ absence: absenceWith(
2484
+ "source_unreadable",
2485
+ "the file text could not be read, so no stored span can be converted to an LSP position (no symbol is placed)",
2486
+ filePath
2487
+ ),
2488
+ omitted: rows.map((row) => ({
2489
+ id: row.id,
2490
+ name: row.name,
2491
+ reason: "source_unreadable"
2492
+ })),
2493
+ staleness,
2494
+ selectionRangeBasis: "declaration_span"
2495
+ };
2496
+ }
2497
+ const byId = /* @__PURE__ */ new Map();
2498
+ const roots = [];
2499
+ const omitted = [];
2500
+ let identifierPinned = 0;
2501
+ for (const row of rows) {
2502
+ const spans = symbolRanges(sourceText, row);
2503
+ if (spans === null) {
2504
+ omitted.push({
2505
+ id: row.id,
2506
+ name: row.name,
2507
+ reason: row.byte_start === null || row.byte_end === null || row.line_start === null || row.line_end === null ? "coordinates_missing" : "range_unresolvable_from_source"
2508
+ });
2509
+ continue;
2510
+ }
2511
+ if (spans.selectionRangeBasis === "identifier_at_stored_start") identifierPinned++;
2512
+ const entry = {
2513
+ id: row.id,
2514
+ name: row.name,
2515
+ qualifiedName: row.qualified_name,
2516
+ symbolType: row.symbol_type,
2517
+ range: spans.range,
2518
+ selectionRange: spans.selectionRange,
2519
+ selectionRangeBasis: spans.selectionRangeBasis,
2520
+ children: []
2521
+ };
2522
+ byId.set(row.id, entry);
2523
+ const parentId = row.owner_symbol_id;
2524
+ const parent = parentId !== null ? byId.get(parentId) : void 0;
2525
+ if (parent !== void 0 && parent.children !== void 0) {
2526
+ parent.children.push(entry);
2527
+ } else {
2528
+ roots.push(entry);
2529
+ }
2530
+ }
2531
+ return {
2532
+ symbols: roots,
2533
+ evidenceTier: "indexed",
2534
+ notIndexed: false,
2535
+ absence: null,
2536
+ omitted,
2537
+ staleness,
2538
+ // The response-level basis is the WEAKEST basis any entry used: a client
2539
+ // that renders one declaration for the whole tree must not claim an
2540
+ // identifier-precision surface when some entries are declaration spans.
2541
+ selectionRangeBasis: identifierPinned === rows.length - omitted.length ? "identifier_at_stored_start" : "declaration_span"
2542
+ };
2543
+ }
2544
+ function workspaceSymbols(raw, request) {
2545
+ const limit = Math.max(
2546
+ 1,
2547
+ Math.min(request.limit ?? MAX_WORKSPACE_SYMBOLS, MAX_WORKSPACE_SYMBOLS)
2548
+ );
2549
+ const query = request.query.trim();
2550
+ const emptyStaleness = {
2551
+ stale: true,
2552
+ status: "unknown",
2553
+ filesChecked: 0,
2554
+ filesUnverified: []
2555
+ };
2556
+ if (query === "") {
2557
+ return {
2558
+ symbols: [],
2559
+ truncated: false,
2560
+ totalCount: 0,
2561
+ limit,
2562
+ evidenceTier: "indexed",
2563
+ absence: absenceWith(
2564
+ "not_in_index",
2565
+ "the query was empty, so no symbol name was searched for"
2566
+ ),
2567
+ omitted: [],
2568
+ staleness: emptyStaleness
2569
+ };
2570
+ }
2571
+ const columns = "SELECT s.id, s.name, s.qualified_name, s.symbol_type, s.owner_symbol_id, s.file_id, s.byte_start, s.byte_end, s.line_start, s.line_end, s.gravity_score, f.path, f.language FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id ";
2572
+ const order = "ORDER BY s.gravity_score DESC, s.name, s.id LIMIT ?";
2573
+ let rows = raw.prepare(columns + "WHERE s.name = ? " + order).all(query, limit);
2574
+ if (rows.length === 0) {
2575
+ rows = raw.prepare(columns + "WHERE s.name LIKE ? " + order).all(`%${query}%`, limit);
2576
+ }
2577
+ const countBase = "SELECT COUNT(*) AS c FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id WHERE s.name = ?";
2578
+ let totalRow = raw.prepare(countBase).get(query);
2579
+ if ((totalRow?.c ?? 0) === 0) {
2580
+ const fuzzyCount = "SELECT COUNT(*) AS c FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id WHERE s.name LIKE ?";
2581
+ totalRow = raw.prepare(fuzzyCount).get(`%${query}%`);
2582
+ }
2583
+ const sourceCache = /* @__PURE__ */ new Map();
2584
+ const readSource = (path3) => {
2585
+ const hit = sourceCache.get(path3);
2586
+ if (hit !== void 0) return hit;
2587
+ let text = null;
2588
+ try {
2589
+ text = readFileSync6(path3, "utf8");
2590
+ } catch {
2591
+ text = null;
2592
+ }
2593
+ sourceCache.set(path3, text);
2594
+ return text;
2595
+ };
2596
+ const symbols = [];
2597
+ const omitted = [];
2598
+ for (const row of rows) {
2599
+ if (row.line_start === null) {
2600
+ omitted.push({
2601
+ id: row.id,
2602
+ name: row.name,
2603
+ reason: "coordinates_missing"
2604
+ });
2605
+ continue;
2606
+ }
2607
+ const text = readSource(row.path);
2608
+ if (text === null) {
2609
+ omitted.push({ id: row.id, name: row.name, reason: "source_unreadable" });
2610
+ continue;
2611
+ }
2612
+ let lspLine;
2613
+ try {
2614
+ lspLine = createPositionMapper(text).indexToLsp({
2615
+ line: row.line_start,
2616
+ column: 1
2617
+ }).line;
2618
+ } catch {
2619
+ omitted.push({
2620
+ id: row.id,
2621
+ name: row.name,
2622
+ reason: "line_outside_file"
2623
+ });
2624
+ continue;
2625
+ }
2626
+ symbols.push({
2627
+ id: row.id,
2628
+ name: row.name,
2629
+ qualifiedName: row.qualified_name,
2630
+ symbolType: row.symbol_type,
2631
+ path: row.path,
2632
+ line: lspLine
2633
+ });
2634
+ }
2635
+ const staleness = markerFor(raw, rows);
2636
+ const totalCount = totalRow?.c ?? rows.length;
2637
+ return {
2638
+ symbols,
2639
+ truncated: totalCount > symbols.length,
2640
+ totalCount,
2641
+ limit,
2642
+ evidenceTier: symbols.length > 0 ? "indexed" : "not_indexed",
2643
+ absence: symbols.length === 0 ? rows.length === 0 ? absenceWith(
2644
+ "not_in_index",
2645
+ `no indexed symbol is named "${query}" (this is an indexed, served language: the answer is "no such symbol", not "no such language")`
2646
+ ) : absenceWith(
2647
+ "coordinates_missing",
2648
+ `every match for "${query}" was omitted because its stored position could not be resolved from the file (${omitted.length} omitted) \u2014 no symbol is placed at an invented line`
2649
+ ) : null,
2650
+ omitted,
2651
+ staleness
2652
+ };
2653
+ }
2654
+
2655
+ // src/lsp/call_hierarchy.ts
2656
+ import { readFileSync as readFileSync7 } from "fs";
2657
+ var CH_MAX_DEPTH = 5;
2658
+ var CH_FANOUT_BUDGET = 50;
2659
+ var CALL_HIERARCHY_RELATIONSHIP_TYPES = ["calls"];
2660
+ var KIND_MAP = {
2661
+ class: 5,
2662
+ // Class
2663
+ interface: 11,
2664
+ // Interface
2665
+ enum: 13,
2666
+ // Enum
2667
+ function: 12,
2668
+ // Function
2669
+ method: 6,
2670
+ // Method
2671
+ constructor: 9,
2672
+ // Constructor
2673
+ variable: 13,
2674
+ // Variable (LSP has no variable kind; 13 is Enum — see below)
2675
+ field: 8,
2676
+ // Field
2677
+ property: 10,
2678
+ // Property
2679
+ struct: 23,
2680
+ // Struct
2681
+ trait: 24,
2682
+ // Trait (LSP SymbolKind has no trait; closest is Interface)
2683
+ macro: 25
2684
+ // Macro
2685
+ };
2686
+ KIND_MAP["variable"] = 13;
2687
+ var CALLABLE_SYMBOL_TYPES2 = /* @__PURE__ */ new Set([
2688
+ "function",
2689
+ "method",
2690
+ "constructor",
2691
+ "procedure",
2692
+ "subroutine",
2693
+ "getter",
2694
+ "setter"
2695
+ ]);
2696
+ var defaultTraverse = traverseGraph;
2697
+ function symbolKindFor(symbolType) {
2698
+ return KIND_MAP[symbolType] ?? 2;
2699
+ }
2700
+ function lspLineWindow(mapper, line, lineEnd) {
2701
+ try {
2702
+ const start = mapper.indexToLsp({ line, column: 1 }).line;
2703
+ const endLine = Number.isInteger(lineEnd) && lineEnd >= line ? lineEnd : line;
2704
+ const end = mapper.indexToLsp({ line: endLine, column: 1 }).line;
2705
+ return { start, end: Math.max(start, end) };
2706
+ } catch {
2707
+ return null;
2708
+ }
2709
+ }
2710
+ function unresolvableRange() {
2711
+ return { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
2712
+ }
2713
+ function emptyCrossCheck() {
2714
+ return {
2715
+ edges: 0,
2716
+ agree: 0,
2717
+ disagree: 0,
2718
+ sourceOnly: 0,
2719
+ storeOnly: 0,
2720
+ neither: 0,
2721
+ addressMissing: 0,
2722
+ direct: 0,
2723
+ hop: 0,
2724
+ directnessUnstated: 0,
2725
+ comparisonNotRun: 0
2726
+ };
2727
+ }
2728
+ function crossCheckEdge(edge, sourceLines0, renderedLine0, edgeTargetName) {
2729
+ const address = edge.callSite ?? null;
2730
+ const sourceLines = [...new Set(sourceLines0)].sort((a, b) => a - b);
2731
+ const storedLine0 = address === null ? null : addressLine0(address);
2732
+ const storeNamesCallSite = address !== null && isExactAddress(address);
2733
+ const depth = typeof edge.depth === "number" ? edge.depth : Number.NaN;
2734
+ const direct = depth === 0;
2735
+ let agreement;
2736
+ if (storeNamesCallSite && storedLine0 !== null && sourceLines.includes(storedLine0)) {
2737
+ agreement = "agree";
2738
+ } else if (storeNamesCallSite && storedLine0 !== null && sourceLines.length > 0) {
2739
+ agreement = "disagree";
2740
+ } else if (storeNamesCallSite && storedLine0 !== null) {
2741
+ agreement = "store-only";
2742
+ } else if (sourceLines.length > 0) {
2743
+ agreement = "source-only";
2744
+ } else {
2745
+ agreement = "neither";
2746
+ }
2747
+ return { agreement, sourceLines0: sourceLines, address, renderedLine0, edgeTargetName, direct, depth };
2748
+ }
2749
+ function tallyCrossCheck(census, agreement, addressPresent) {
2750
+ census.edges++;
2751
+ if (!addressPresent) census.addressMissing++;
2752
+ const depth = agreement.depth;
2753
+ if (depth === 0) census.direct++;
2754
+ else if (Number.isInteger(depth) && depth >= 1) census.hop++;
2755
+ else census.directnessUnstated++;
2756
+ switch (agreement.agreement) {
2757
+ case "agree":
2758
+ census.agree++;
2759
+ break;
2760
+ case "disagree":
2761
+ census.disagree++;
2762
+ break;
2763
+ case "source-only":
2764
+ census.sourceOnly++;
2765
+ break;
2766
+ case "store-only":
2767
+ census.storeOnly++;
2768
+ break;
2769
+ case "neither":
2770
+ census.neither++;
2771
+ break;
2772
+ }
2773
+ }
2774
+ function declarationTokenOf(sourceText, mapper, name, declLine1) {
2775
+ const window = lspLineWindow(mapper, declLine1, declLine1);
2776
+ if (window === null) return void 0;
2777
+ const token = nameTokenRange(sourceText, name, { start: window.start, end: window.start });
2778
+ return token === null ? void 0 : token.start;
2779
+ }
2780
+ function unknownStaleness(paths = []) {
2781
+ return { stale: true, status: "unknown", filesChecked: 0, filesUnverified: paths };
2782
+ }
2783
+ function symbolStaleness(raw, fileId, filePath) {
2784
+ const status = checkFileFreshness(raw, fileId);
2785
+ if (status === null) return unknownStaleness([filePath]);
2786
+ return {
2787
+ stale: status.status !== "fresh",
2788
+ status: status.status,
2789
+ filesChecked: 1,
2790
+ filesUnverified: []
2791
+ };
2792
+ }
2793
+ function expandStaleness(raw, rootFileId, rootFilePath, edgePaths) {
2794
+ const distinct = [...new Set(edgePaths)].filter((p) => p !== "" && p !== rootFilePath);
2795
+ const idByPath = /* @__PURE__ */ new Map();
2796
+ if (distinct.length > 0) {
2797
+ const placeholders = distinct.map(() => "?").join(", ");
2798
+ let rows = [];
2799
+ try {
2800
+ rows = raw.prepare(`SELECT id, path FROM brain_file WHERE path IN (${placeholders})`).all(...distinct);
2801
+ } catch {
2802
+ rows = [];
2803
+ }
2804
+ for (const row of rows) idByPath.set(row.path, row.id);
2805
+ }
2806
+ let fresh = 0;
2807
+ let stale = 0;
2808
+ let missing = 0;
2809
+ const unverified = [];
2810
+ const account = (fileId, filePath) => {
2811
+ if (fileId === null) {
2812
+ unverified.push(filePath);
2813
+ return;
2814
+ }
2815
+ const status = checkFileFreshness(raw, fileId);
2816
+ if (status === null) unverified.push(filePath);
2817
+ else if (status.status === "fresh") fresh++;
2818
+ else if (status.status === "stale") stale++;
2819
+ else missing++;
2820
+ };
2821
+ account(rootFileId, rootFilePath);
2822
+ for (const path3 of distinct) account(idByPath.get(path3) ?? null, path3);
2823
+ return {
2824
+ stale: stale > 0 || missing > 0 || unverified.length > 0,
2825
+ status: missing > 0 ? "missing" : stale > 0 ? "stale" : unverified.length > 0 ? "unknown" : fresh > 0 ? "fresh" : "unknown",
2826
+ filesChecked: 1 + distinct.length - unverified.length,
2827
+ filesUnverified: unverified
2828
+ };
2829
+ }
2830
+ var WORD_CHAR2 = /[A-Za-z0-9_$]/;
2831
+ function isWordChar2(ch) {
2832
+ return ch !== void 0 && WORD_CHAR2.test(ch);
2833
+ }
2834
+ function codeBeforeComment(line) {
2835
+ const i = line.indexOf("//");
2836
+ return i === -1 ? line : line.slice(0, i);
2837
+ }
2838
+ function receiverBefore(line, at) {
2839
+ let i = at - 1;
2840
+ while (i >= 0 && /\s/.test(line[i])) i--;
2841
+ if (line[i] !== ".") return "";
2842
+ let j = i - 1;
2843
+ while (j >= 0 && /\s/.test(line[j])) j--;
2844
+ const end = j + 1;
2845
+ while (j >= 0 && (isWordChar2(line[j]) || line[j] === ".")) j--;
2846
+ return line.slice(j + 1, end).replace(/\.+$/, "");
2847
+ }
2848
+ function classify(receiver, isConstruct, ownNames) {
2849
+ if (isConstruct) return "construct";
2850
+ if (receiver === "") return "plain";
2851
+ const segments = receiver.split(".").filter((s) => s !== "");
2852
+ const last = segments[segments.length - 1] ?? "";
2853
+ if (last === "this") return "this";
2854
+ if (ownNames.has(last)) return "this";
2855
+ return "receiver";
2856
+ }
2857
+ function resolveInvocations(sourceText, targetName, callerLines0, skip, ownNames = /* @__PURE__ */ new Set(), skipDeclarations = []) {
2858
+ if (targetName.length === 0) return [];
2859
+ const mapper = createPositionMapper(sourceText);
2860
+ const out = [];
2861
+ const last = Math.min(callerLines0.end, mapper.lineTexts.length - 1);
2862
+ const excluded = skip === void 0 ? skipDeclarations : [skip, ...skipDeclarations];
2863
+ for (let line0 = Math.max(0, callerLines0.start); line0 <= last; line0++) {
2864
+ const line = codeBeforeComment(mapper.lineTexts[line0] ?? "");
2865
+ let from = 0;
2866
+ for (; ; ) {
2867
+ const at = line.indexOf(targetName, from);
2868
+ if (at === -1) break;
2869
+ from = at + targetName.length;
2870
+ if (isWordChar2(line[at - 1]) || isWordChar2(line[at + targetName.length])) continue;
2871
+ if (excluded.some((p) => p.line === line0 && p.character === at)) continue;
2872
+ let k = at + targetName.length;
2873
+ while (k < line.length && /\s/.test(line[k])) k++;
2874
+ const isCall = line[k] === "(";
2875
+ const isConstruct = /(^|[^\w$])new\s+$/.test(line.slice(0, at));
2876
+ if (!isCall && !isConstruct) continue;
2877
+ const receiver = receiverBefore(line, at);
2878
+ let range;
2879
+ try {
2880
+ const start = mapper.lspToIndex({ line: line0, character: at });
2881
+ const end = mapper.lspToIndex({ line: line0, character: at + targetName.length });
2882
+ range = { start: mapper.indexToLsp(start), end: mapper.indexToLsp(end) };
2883
+ } catch {
2884
+ continue;
2885
+ }
2886
+ out.push({
2887
+ range,
2888
+ kind: classify(receiver, isConstruct, ownNames),
2889
+ receiver
2890
+ });
2891
+ }
2892
+ }
2893
+ return out;
2894
+ }
2895
+ function nameTokenRange(sourceText, name, lines0) {
2896
+ if (name.length === 0) return null;
2897
+ const mapper = createPositionMapper(sourceText);
2898
+ const last = Math.min(lines0.end, mapper.lineTexts.length - 1);
2899
+ for (let line0 = Math.max(0, lines0.start); line0 <= last; line0++) {
2900
+ const line = mapper.lineTexts[line0] ?? "";
2901
+ let from = 0;
2902
+ for (; ; ) {
2903
+ const at = line.indexOf(name, from);
2904
+ if (at === -1) break;
2905
+ from = at + name.length;
2906
+ if (isWordChar2(line[at - 1]) || isWordChar2(line[at + name.length])) continue;
2907
+ try {
2908
+ const start = mapper.lspToIndex({ line: line0, character: at });
2909
+ const end = mapper.lspToIndex({ line: line0, character: at + name.length });
2910
+ return { start: mapper.indexToLsp(start), end: mapper.indexToLsp(end) };
2911
+ } catch {
2912
+ return null;
2913
+ }
2914
+ }
2915
+ }
2916
+ return null;
2917
+ }
2918
+ function lineIndent(sourceText, line0) {
2919
+ const line = createPositionMapper(sourceText).lineTexts[line0] ?? "";
2920
+ const m = /^(\s*)/.exec(line);
2921
+ return (m?.[1] ?? "").length;
2922
+ }
2923
+ function lineLength(sourceText, line0) {
2924
+ return (createPositionMapper(sourceText).lineTexts[line0] ?? "").length;
2925
+ }
2926
+ function makeSourceReader() {
2927
+ const cache = /* @__PURE__ */ new Map();
2928
+ return (filePath) => {
2929
+ if (filePath === "") return null;
2930
+ const hit = cache.get(filePath);
2931
+ if (hit !== void 0) return hit;
2932
+ let text = null;
2933
+ try {
2934
+ text = readFileSync7(filePath, "utf8");
2935
+ } catch {
2936
+ text = null;
2937
+ }
2938
+ cache.set(filePath, text);
2939
+ return text;
2940
+ };
2941
+ }
2942
+ function prepareCallHierarchy(raw, request) {
2943
+ const scope = declaredAbsenceFor(request.filePath);
2944
+ if (scope !== null) {
2945
+ return { item: null, notIndexed: true, absence: scope, staleness: unknownStaleness([request.filePath]) };
2946
+ }
2947
+ const symbol = findIndexedSymbol(raw, request.filePath, request.line, request.character);
2948
+ if (symbol === null) {
2949
+ return {
2950
+ item: null,
2951
+ notIndexed: true,
2952
+ absence: absenceWith(
2953
+ "symbol_not_at_position",
2954
+ "the index stores no symbol covering this position (this is a served language: the answer is 'no symbol here', not 'no call hierarchy')",
2955
+ request.filePath
2956
+ ),
2957
+ staleness: unknownStaleness([request.filePath])
2958
+ };
2959
+ }
2960
+ const staleness = symbolStaleness(raw, symbol.file_id, symbol.path);
2961
+ if (!CALLABLE_SYMBOL_TYPES2.has((symbol.symbol_type ?? "").toLowerCase())) {
2962
+ return {
2963
+ item: null,
2964
+ notIndexed: true,
2965
+ absence: absenceWith(
2966
+ "no_stored_calls",
2967
+ `symbol kind '${symbol.symbol_type}' is not callable \u2014 call hierarchy does not apply (this is NOT 'no callers': the surface has no callable identity for this kind)`,
2968
+ request.filePath
2969
+ ),
2970
+ staleness
2971
+ };
2972
+ }
2973
+ let sourceText;
2974
+ try {
2975
+ sourceText = readFileSync7(symbol.path, "utf8");
2976
+ } catch {
2977
+ return {
2978
+ item: null,
2979
+ notIndexed: true,
2980
+ absence: absenceWith(
2981
+ "source_unreadable",
2982
+ "the file text could not be read, so no stored span can be converted to an LSP position (no item is placed)",
2983
+ symbol.path
2984
+ ),
2985
+ staleness
2986
+ };
2987
+ }
2988
+ if (symbol.byte_start === null || symbol.byte_end === null || symbol.line_start === null || symbol.line_end === null) {
2989
+ return {
2990
+ item: null,
2991
+ notIndexed: true,
2992
+ absence: absenceWith(
2993
+ "coordinates_missing",
2994
+ "the indexed symbol stored no byte/line coordinates, so no position can be derived from it (no item is placed)",
2995
+ symbol.path
2996
+ ),
2997
+ staleness
2998
+ };
2999
+ }
3000
+ const startCol = resolveRowByteColumns(sourceText, symbol.line_start, symbol.byte_start);
3001
+ const endCol = resolveRowByteColumns(sourceText, symbol.line_end, symbol.byte_end);
3002
+ if (startCol === null || endCol === null) {
3003
+ return {
3004
+ item: null,
3005
+ notIndexed: true,
3006
+ absence: absenceWith(
3007
+ "coordinates_missing",
3008
+ "the indexed span could not be resolved against the file's own byte columns (no item is placed)",
3009
+ symbol.path
3010
+ ),
3011
+ staleness
3012
+ };
3013
+ }
3014
+ const mapper = createPositionMapper(sourceText);
3015
+ const range = mapper.indexRangeToLsp({
3016
+ start: { line: startCol.line, column: startCol.column },
3017
+ end: { line: endCol.line, column: endCol.column }
3018
+ });
3019
+ const lspRange = {
3020
+ start: { line: range.start.line, character: range.start.character },
3021
+ end: { line: range.end.line, character: range.end.character }
3022
+ };
3023
+ const nameRange = nameTokenRange(
3024
+ sourceText,
3025
+ symbol.name,
3026
+ { start: lspRange.start.line, end: lspRange.end.line }
3027
+ ) ?? lspRange;
3028
+ const sigRow = raw.prepare("SELECT parameters, return_type, visibility FROM brain_symbol WHERE id = ?").get(symbol.id);
3029
+ const detail = sigRow === void 0 ? symbol.symbol_type : renderSignature(
3030
+ {
3031
+ name: symbol.name,
3032
+ symbol_type: symbol.symbol_type,
3033
+ parameters: sigRow.parameters,
3034
+ return_type: sigRow.return_type,
3035
+ visibility: null
3036
+ },
3037
+ languageForPath(symbol.path)
3038
+ );
3039
+ return {
3040
+ item: {
3041
+ name: symbol.name,
3042
+ kind: symbolKindFor(symbol.symbol_type),
3043
+ uri: `file://${symbol.path}`,
3044
+ range: lspRange,
3045
+ selectionRange: nameRange,
3046
+ detail,
3047
+ positionBasis: "source_resolved",
3048
+ data: { symbolId: symbol.id, isExternal: false }
3049
+ },
3050
+ notIndexed: false,
3051
+ absence: null,
3052
+ staleness
3053
+ };
3054
+ }
3055
+ function itemFromNode(node, source, detail) {
3056
+ const filePath = node.filePath !== "" ? node.filePath : node.file;
3057
+ const mapper = source === null ? null : createPositionMapper(source);
3058
+ const window = mapper === null ? null : lspLineWindow(mapper, node.line, node.lineEnd);
3059
+ const start0 = window === null ? 0 : window.start;
3060
+ const end0 = window === null ? 0 : window.end;
3061
+ let range = unresolvableRange();
3062
+ let selectionRange = range;
3063
+ if (source !== null && window !== null) {
3064
+ range = {
3065
+ start: { line: start0, character: lineIndent(source, start0) },
3066
+ end: { line: end0, character: lineLength(source, end0) }
3067
+ };
3068
+ selectionRange = nameTokenRange(source, node.name, { start: start0, end: end0 }) ?? range;
3069
+ }
3070
+ const resolved = source !== null && window !== null;
3071
+ return {
3072
+ name: node.name,
3073
+ kind: symbolKindFor(node.symbolType),
3074
+ uri: filePath !== "" ? `file://${filePath}` : "",
3075
+ range,
3076
+ selectionRange,
3077
+ detail: resolved ? detail : `${detail} \xB7 position unresolvable (no readable source for this node)`,
3078
+ positionBasis: resolved ? "source_resolved" : "declared_unresolvable",
3079
+ data: { symbolId: node.id, isExternal: node.isExternal }
3080
+ };
3081
+ }
3082
+ async function expandCalls(raw, projectId, request, traverse = defaultTraverse, readSource = makeSourceReader()) {
3083
+ const { item } = request;
3084
+ if (item.data.isExternal) {
3085
+ return {
3086
+ calls: [],
3087
+ truncated: false,
3088
+ maxDepthReached: 0,
3089
+ evidenceTier: "not_indexed",
3090
+ callSitesResolved: 0,
3091
+ callSitesApproximate: 0,
3092
+ droppedEdges: [],
3093
+ absence: absenceWith(
3094
+ "external_target",
3095
+ "the expanded item is an external node with no index identity, so there is nothing for bb to traverse (this is NOT 'this symbol has no callers')"
3096
+ ),
3097
+ declaredAbsentNodes: [],
3098
+ staleness: unknownStaleness(),
3099
+ callSiteEntries: { resolved: 0, approximate: 0 },
3100
+ addressCrossCheck: emptyCrossCheck(),
3101
+ addressDisagreements: [],
3102
+ relationshipTypes: [...CALL_HIERARCHY_RELATIONSHIP_TYPES]
3103
+ };
3104
+ }
3105
+ const rootRow = raw.prepare(
3106
+ `SELECT s.id, s.name, s.symbol_type, COALESCE(s.qualified_name,'') AS qualifiedName,
3107
+ COALESCE(s.line_start,0) AS line,
3108
+ COALESCE(s.line_end,0) AS lineEnd,
3109
+ COALESCE(f.relative_path,'') AS relativePath,
3110
+ COALESCE(f.path,'') AS filePath,
3111
+ f.id AS fileId,
3112
+ COALESCE(f.language,'unknown') AS language,
3113
+ COALESCE(s.complexity,0) AS complexity
3114
+ FROM brain_symbol s JOIN brain_file f ON f.id = s.file_id
3115
+ WHERE s.id = ?`
3116
+ ).get(item.data.symbolId);
3117
+ if (rootRow === void 0) {
3118
+ return {
3119
+ calls: [],
3120
+ truncated: false,
3121
+ maxDepthReached: 0,
3122
+ evidenceTier: "not_indexed",
3123
+ callSitesResolved: 0,
3124
+ callSitesApproximate: 0,
3125
+ droppedEdges: [],
3126
+ absence: absenceWith(
3127
+ "symbol_not_in_index",
3128
+ `the index stores no symbol with id "${item.data.symbolId}", so there is no hierarchy to traverse (this is NOT 'this symbol has no callers')`
3129
+ ),
3130
+ declaredAbsentNodes: [],
3131
+ staleness: unknownStaleness(),
3132
+ callSiteEntries: { resolved: 0, approximate: 0 },
3133
+ addressCrossCheck: emptyCrossCheck(),
3134
+ addressDisagreements: [],
3135
+ relationshipTypes: [...CALL_HIERARCHY_RELATIONSHIP_TYPES]
3136
+ };
3137
+ }
3138
+ const rootScope = declaredAbsenceFor(rootRow.filePath);
3139
+ if (rootScope !== null) {
3140
+ return {
3141
+ calls: [],
3142
+ truncated: false,
3143
+ maxDepthReached: 0,
3144
+ evidenceTier: "not_indexed",
3145
+ callSitesResolved: 0,
3146
+ callSitesApproximate: 0,
3147
+ droppedEdges: [],
3148
+ absence: rootScope,
3149
+ declaredAbsentNodes: [],
3150
+ staleness: unknownStaleness([rootRow.filePath]),
3151
+ callSiteEntries: { resolved: 0, approximate: 0 },
3152
+ addressCrossCheck: emptyCrossCheck(),
3153
+ addressDisagreements: [],
3154
+ relationshipTypes: [...CALL_HIERARCHY_RELATIONSHIP_TYPES]
3155
+ };
3156
+ }
3157
+ const ownNames = /* @__PURE__ */ new Set([rootRow.name]);
3158
+ const qualified = rootRow.qualifiedName ?? "";
3159
+ if (qualified.includes(".")) {
3160
+ const ownerSegment = qualified.slice(0, qualified.lastIndexOf(".")).split(".").pop() ?? "";
3161
+ if (ownerSegment.length > 0) ownNames.add(ownerSegment);
3162
+ }
3163
+ const maxDepth = Math.max(1, Math.min(request.maxDepth ?? CH_MAX_DEPTH, 10));
3164
+ const direction = request.direction === "incoming" ? "callers" : "callees";
3165
+ const engineRoot = {
3166
+ id: rootRow.id,
3167
+ name: rootRow.name,
3168
+ symbolType: rootRow.symbol_type,
3169
+ line: rootRow.line,
3170
+ lineEnd: rootRow.lineEnd,
3171
+ relativePath: rootRow.relativePath,
3172
+ filePath: rootRow.filePath,
3173
+ language: rootRow.language,
3174
+ complexity: rootRow.complexity
3175
+ };
3176
+ const result = await traverse(projectId, engineRoot, {
3177
+ direction,
3178
+ maxDepth,
3179
+ callerBudget: CH_FANOUT_BUDGET,
3180
+ calleeBudget: CH_FANOUT_BUDGET,
3181
+ // §15.3: ask for CALL rows only. Without this the engine's default ten-kind
3182
+ // superset reaches a renderer whose `fromRanges` must name a call site.
3183
+ relationshipTypes: [...CALL_HIERARCHY_RELATIONSHIP_TYPES]
3184
+ });
3185
+ const rootSource = readSource(rootRow.filePath);
3186
+ const edges = direction === "callers" ? result.callers : result.callees;
3187
+ const calls = [];
3188
+ const seenFrom = /* @__PURE__ */ new Set();
3189
+ const droppedEdges = [];
3190
+ const declaredAbsentNodes = [];
3191
+ const addressDisagreements = [];
3192
+ const edgePaths = [];
3193
+ let callSitesResolved = 0;
3194
+ let callSitesApproximate = 0;
3195
+ let resolvedEntries = 0;
3196
+ let approximateEntries = 0;
3197
+ const crossCheck = emptyCrossCheck();
3198
+ for (const edge of edges) {
3199
+ const otherId = direction === "callers" ? edge.from : edge.to;
3200
+ if (seenFrom.has(otherId)) continue;
3201
+ const node = result.nodes[otherId];
3202
+ if (node === void 0) continue;
3203
+ const nodePath = node.filePath !== "" ? node.filePath : node.file;
3204
+ if (!node.isExternal && declaredAbsenceFor(nodePath) !== null) {
3205
+ declaredAbsentNodes.push({
3206
+ name: node.name,
3207
+ symbolId: otherId,
3208
+ reason: `the node's own file (${nodePath}) is outside the bb launch set, so bb declares no call sites for it`
3209
+ });
3210
+ continue;
3211
+ }
3212
+ edgePaths.push(nodePath);
3213
+ const callerSource = direction === "callers" ? readSource(nodePath) : rootSource;
3214
+ const callerWindow = callerSource === null ? null : lspLineWindow(
3215
+ createPositionMapper(callerSource),
3216
+ direction === "callers" ? node.line : rootRow.line,
3217
+ direction === "callers" ? node.lineEnd : rootRow.lineEnd
3218
+ );
3219
+ const callerLines0 = callerWindow;
3220
+ const invokedName = direction === "callers" ? rootRow.name : node.name;
3221
+ const callerOwnName = direction === "callers" ? node.name : rootRow.name;
3222
+ const callerNamePos = callerSource === null || callerLines0 === null ? void 0 : nameTokenRange(callerSource, callerOwnName, callerLines0)?.start;
3223
+ const scannedPath = direction === "callers" ? nodePath : rootRow.filePath;
3224
+ const targetDeclLine1 = direction === "callers" ? rootRow.line : node.line;
3225
+ const targetDeclPath = direction === "callers" ? rootRow.filePath : nodePath;
3226
+ const targetDeclPos = callerSource === null || scannedPath !== targetDeclPath ? void 0 : declarationTokenOf(callerSource, createPositionMapper(callerSource), invokedName, targetDeclLine1);
3227
+ const invocations = callerSource === null || callerLines0 === null ? [] : resolveInvocations(
3228
+ callerSource,
3229
+ invokedName,
3230
+ callerLines0,
3231
+ callerNamePos,
3232
+ ownNames,
3233
+ targetDeclPos === void 0 ? [] : [targetDeclPos]
3234
+ );
3235
+ const isSelfEdge = otherId === item.data.symbolId;
3236
+ if (isSelfEdge) {
3237
+ const receiverQualified = invocations.length > 0 && invocations.every((i) => i.kind === "receiver");
3238
+ if (receiverQualified) {
3239
+ droppedEdges.push({
3240
+ name: node.name,
3241
+ symbolId: otherId,
3242
+ reason: `receiver-qualified self-name call (${invocations[0].receiver}.${invokedName}) targets another value, not this symbol`
3243
+ });
3244
+ continue;
3245
+ }
3246
+ }
3247
+ const scanLines0 = invocations.map((i) => i.range.start.line);
3248
+ const edgeTarget = result.nodes[edge.to];
3249
+ const edgeTargetName = edgeTarget?.name ?? "";
3250
+ const edgeSource = result.nodes[edge.from];
3251
+ const likeForLike = edge.depth === 0 && edgeTargetName === invokedName && edgeSource !== void 0;
3252
+ let crossLines0 = scanLines0;
3253
+ let crossScanRan = true;
3254
+ if (!likeForLike) {
3255
+ crossScanRan = false;
3256
+ const hopPath = edgeSource === void 0 ? "" : edgeSource.filePath !== "" ? edgeSource.filePath : edgeSource.file;
3257
+ const hopSource = hopPath === "" ? null : readSource(hopPath);
3258
+ if (hopSource !== null && edgeSource !== void 0 && edgeTargetName !== "") {
3259
+ const hopMapper = createPositionMapper(hopSource);
3260
+ const hopWindow = lspLineWindow(hopMapper, edgeSource.line, edgeSource.lineEnd);
3261
+ if (hopWindow !== null) {
3262
+ const hopTargetPath = edgeTarget === void 0 ? "" : edgeTarget.filePath !== "" ? edgeTarget.filePath : edgeTarget.file;
3263
+ const hopDeclPos = hopTargetPath !== "" && hopPath === hopTargetPath ? declarationTokenOf(hopSource, hopMapper, edgeTargetName, edgeTarget.line) : void 0;
3264
+ crossLines0 = resolveInvocations(
3265
+ hopSource,
3266
+ edgeTargetName,
3267
+ hopWindow,
3268
+ void 0,
3269
+ /* @__PURE__ */ new Set(),
3270
+ hopDeclPos === void 0 ? [] : [hopDeclPos]
3271
+ ).map((i) => i.range.start.line);
3272
+ crossScanRan = true;
3273
+ }
3274
+ }
3275
+ }
3276
+ const agreement = crossCheckEdge(edge, crossLines0, scanLines0[0] ?? null, edgeTargetName);
3277
+ if (!crossScanRan) agreement.agreement = "neither";
3278
+ tallyCrossCheck(crossCheck, agreement, agreement.address !== null);
3279
+ if (!crossScanRan) crossCheck.comparisonNotRun++;
3280
+ if (!CALL_HIERARCHY_RELATIONSHIP_TYPES.includes(edge.relationshipType)) {
3281
+ droppedEdges.push({
3282
+ name: node.name,
3283
+ symbolId: otherId,
3284
+ reason: `the stored row is a "${edge.relationshipType}" relation, not a call; a call hierarchy renders call sites and this relation names none, so the row is omitted rather than rendered with a range (contract 15.3)`
3285
+ });
3286
+ continue;
3287
+ }
3288
+ if (invocations.length === 0 && callerSource !== null && callerLines0 !== null && !node.isExternal && edge.relationshipType === "calls") {
3289
+ const hop = Number.isInteger(edge.depth) && edge.depth >= 1;
3290
+ const storeLine = agreement.address !== null && isExactAddress(agreement.address) ? agreement.address.line : null;
3291
+ const reason = hop ? `${node.name} does not invoke ${invokedName} in its own source; the engine reports this row at depth ${edge.depth}, so it is a transitive hop (${node.name} reaches ${invokedName} through an intermediate symbol), not a direct ${direction === "callers" ? "caller" : "callee"}` : storeLine !== null ? `${node.name} does not invoke ${invokedName} anywhere in its own source, yet the stored row names call site line ${storeLine} - the store and the caller's own text disagree about this DIRECT row; this surface renders the source-resolved position and declares the difference (contract 15.1), because rendering the stored line would be rendering a position this surface cannot verify (15.3)` : `${node.name} does not invoke ${invokedName} in its own source and the stored row names no call site for it - a stale stored row, not a direct ${direction === "callers" ? "caller" : "callee"}`;
3292
+ droppedEdges.push({ name: node.name, symbolId: otherId, reason });
3293
+ if (agreement.agreement !== "neither") {
3294
+ addressDisagreements.push({
3295
+ name: node.name,
3296
+ symbolId: otherId,
3297
+ reason,
3298
+ agreement
3299
+ });
3300
+ }
3301
+ continue;
3302
+ }
3303
+ seenFrom.add(otherId);
3304
+ const recursiveMarker = isSelfEdge ? " \xB7 recursive" : "";
3305
+ const unplaceable = callerLines0 === null && callerSource !== null;
3306
+ const baseDetail = `${node.symbolType} \xB7 ${edge.relationshipType} \xB7 ${recursiveMarker ? "self" : "1 call site"}${recursiveMarker}`;
3307
+ const detail = unplaceable ? `${baseDetail} \xB7 caller declaration line unresolvable in its own source` : baseDetail;
3308
+ const expanded = itemFromNode(node, direction === "callers" ? callerSource : readSource(nodePath), detail);
3309
+ let fromRanges;
3310
+ if (invocations.length > 0) {
3311
+ fromRanges = invocations.map((i) => i.range);
3312
+ callSitesResolved += invocations.length;
3313
+ resolvedEntries++;
3314
+ } else {
3315
+ fromRanges = [expanded.selectionRange];
3316
+ callSitesApproximate += 1;
3317
+ approximateEntries++;
3318
+ }
3319
+ const withPosition = invocations.length === 0 ? { ...expanded, detail: `${expanded.detail} \xB7 position approximate` } : expanded;
3320
+ if (direction === "callers") {
3321
+ calls.push({ from: withPosition, fromRanges });
3322
+ } else {
3323
+ calls.push({ to: withPosition, fromRanges });
3324
+ }
3325
+ if (agreement.agreement !== "agree" && agreement.agreement !== "neither") {
3326
+ const storeLineText = agreement.address !== null && isExactAddress(agreement.address) ? `${agreement.address.file}:${agreement.address.line}` : agreement.address === null ? "no address at all" : `address kind "${agreement.address.kind}"`;
3327
+ addressDisagreements.push({
3328
+ name: node.name,
3329
+ symbolId: otherId,
3330
+ reason: agreement.agreement === "disagree" ? `the store names call site ${storeLineText} while a like-for-like scan of ${agreement.edgeTargetName} in the row's own source resolved ${JSON.stringify(agreement.sourceLines0.map((l) => l + 1))} (1-based) - the two sources of truth contradict each other for this ${agreement.direct ? "direct" : `depth-${agreement.depth} hop`} row, and this surface renders the source-resolved position` : agreement.agreement === "store-only" ? `the store names call site ${storeLineText} and a like-for-like scan of ${agreement.edgeTargetName} in the row's own source resolves nothing - the invocation is not in the text this surface reads (a macro, a template or a dynamic dispatch), on a ${agreement.direct ? "direct" : `depth-${agreement.depth} hop`} row` : `a like-for-like scan resolved ${JSON.stringify(agreement.sourceLines0.map((l) => l + 1))} (1-based) while the store's address is ${storeLineText} - the store names no call site for this row`,
3331
+ agreement: { ...agreement, renderedLine0: fromRanges[0]?.start.line ?? null }
3332
+ });
3333
+ }
3334
+ }
3335
+ return {
3336
+ calls,
3337
+ truncated: result.truncated,
3338
+ truncationReason: result.truncationReason,
3339
+ maxDepthReached: result.maxDepthReached,
3340
+ evidenceTier: "indexed",
3341
+ callSitesResolved,
3342
+ callSitesApproximate,
3343
+ callSiteEntries: { resolved: resolvedEntries, approximate: approximateEntries },
3344
+ droppedEdges,
3345
+ absence: calls.length === 0 ? absenceWith(
3346
+ // §11.7: the symbol WAS found and served; what is empty is its call
3347
+ // list among the kinds this surface reads. That is a different fact
3348
+ // from "no symbol covers the position" (prepare's code), so it has its
3349
+ // own machine-readable code — a client that branches on the code must
3350
+ // not be told the symbol is missing.
3351
+ "no_stored_calls",
3352
+ // §11.7 + §15.1: the absence names the filter, because an answer
3353
+ // emptied BY the filter is a different claim from an answer emptied
3354
+ // because the STORE holds no row.
3355
+ //
3356
+ // WHAT EACH BRANCH MAY CLAIM, kept strictly to what this answer read.
3357
+ // The surface asks the engine for `calls` alone (measured on the T2
3358
+ // fixture at HEAD: `get/incoming` answers zero caller rows), so no
3359
+ // branch may claim what the engine would have said about another kind:
3360
+ // edges.length === 0 no stored CALL row reached this answer. "none
3361
+ // stored" is scoped to the kinds this surface
3362
+ // read, and the sentence says so.
3363
+ // edges.length > 0 this many stored CALL rows were read and this
3364
+ // many were dropped or declared absent. A
3365
+ // non-call row the surface DID see is named in
3366
+ // `droppedEdges` with its kind, so a reader can
3367
+ // find the rows this answer declined rather
3368
+ // than being told they do not exist.
3369
+ //
3370
+ // DELIBERATELY NOT CLAIMED: that the engine returned no stored edge of
3371
+ // any kind, or that the store holds no relationship row of another
3372
+ // kind. This surface asked for one kind and cannot know either. The
3373
+ // pre-M55 wording of the first branch claimed both ("the engine
3374
+ // returned no stored … edge of any kind"), which a filtered surface is
3375
+ // not entitled to say; the declared filter is what lets a reader bound
3376
+ // the claim.
3377
+ edges.length === 0 ? `no stored ${direction === "callers" ? "caller" : "callee"} CALL edge was read for this symbol (none stored among the relationship kind(s) this surface reads). This surface asked the engine for ${JSON.stringify(CALL_HIERARCHY_RELATIONSHIP_TYPES)} only and declares that filter (\xA715.1), so "none stored" is a statement about ${JSON.stringify(CALL_HIERARCHY_RELATIONSHIP_TYPES)} only \u2014 it is NOT a statement that the store holds no relationship row of any other kind` : `no stored ${direction === "callers" ? "caller" : "callee"} CALL row of this symbol is rendered. This surface reads the ${CALL_HIERARCHY_RELATIONSHIP_TYPES.length} relationship kind(s) ${JSON.stringify(CALL_HIERARCHY_RELATIONSHIP_TYPES)} only and declares that filter on every response (\xA715.1): ${edges.length} stored CALL row(s) were read and ${droppedEdges.length} were dropped or declared absent, each with its reason and kind named \u2014 rows of other kinds may be stored for this symbol and are not ${direction === "callers" ? "callers" : "callees"} here, so this answer is "no stored call rendered", not "no stored relationship"`,
3378
+ rootRow.filePath
3379
+ ) : null,
3380
+ declaredAbsentNodes,
3381
+ staleness: expandStaleness(raw, rootRow.fileId ?? null, rootRow.filePath, edgePaths),
3382
+ addressCrossCheck: crossCheck,
3383
+ addressDisagreements,
3384
+ // §15.1: stated on the NORMAL answer too, not only on the absence paths —
3385
+ // a reader of a non-empty caller list needs the same filter declaration as
3386
+ // a reader of an empty one.
3387
+ relationshipTypes: [...CALL_HIERARCHY_RELATIONSHIP_TYPES]
3388
+ };
3389
+ }
3390
+
3391
+ // src/lsp/server.ts
3392
+ var STALENESS_SCHEMA_VERSION = 1;
3393
+ function stalenessProviderPayload() {
3394
+ return {
3395
+ schemaVersion: STALENESS_SCHEMA_VERSION,
3396
+ marker: {
3397
+ stale: "boolean",
3398
+ status: ["fresh", "stale", "missing", "unknown"],
3399
+ pendingReindex: "boolean"
3400
+ },
3401
+ /** The declared sync model (contract §4.2): filesystem-side, debounced by
3402
+ * DEBOUNCE_MS in src/lsp/freshness.ts, with a scan-on-read fallback when
3403
+ * the watcher fails. No latency figure is advertised — this gate measures
3404
+ * none, and an unmeasured budget presented as a budget is a §1 claim
3405
+ * violation. */
3406
+ syncModel: {
3407
+ source: "filesystem-watch",
3408
+ debounceMs: 300,
3409
+ fallback: "scan-on-read",
3410
+ clientDocumentSync: false
3411
+ }
3412
+ };
3413
+ }
3414
+ function impactProviderPayload() {
3415
+ return { schemaVersion: 1 };
3416
+ }
3417
+ function absenceProviderPayload() {
3418
+ return {
3419
+ schemaVersion: ABSENCE_SCHEMA_VERSION,
3420
+ method: ABSENCE_METHOD,
3421
+ direction: "serverToClient",
3422
+ carriers: [ABSENCE_METHOD, "window/logMessage"],
3423
+ /** The reason vocabulary, advertised so a client renders known reasons
3424
+ * correctly instead of string-matching on prose. */
3425
+ reasons: ["language_not_launched", "not_indexed", "unresolvable_location", "unsupported_language", "not_callable"],
3426
+ /** What a client is being told, stated plainly: an EMPTY standard result
3427
+ * on these methods is not evidence that nothing exists. */
3428
+ emptyResultMeans: "absent-or-unserved \u2014 not 'nothing found'; read the bb/absence notice for which"
3429
+ };
3430
+ }
3431
+ function workspaceProviderPayload() {
3432
+ return { workspaceFolders: { supported: false, changeNotifications: false } };
3433
+ }
3434
+ var CAPABILITY_COVERAGE = [
3435
+ { key: "hoverProvider", methods: ["textDocument/hover"], kind: "handler", value: () => true },
3436
+ { key: "definitionProvider", methods: ["textDocument/definition"], kind: "handler", value: () => true },
3437
+ { key: "referencesProvider", methods: ["textDocument/references"], kind: "handler", value: () => true },
3438
+ {
3439
+ key: "documentHighlightProvider",
3440
+ methods: ["textDocument/documentHighlight"],
3441
+ kind: "handler",
3442
+ value: () => true
3443
+ },
3444
+ { key: "documentSymbolProvider", methods: ["textDocument/documentSymbol"], kind: "handler", value: () => true },
3445
+ { key: "workspaceSymbolProvider", methods: ["workspace/symbol"], kind: "handler", value: () => true },
3446
+ {
3447
+ key: "callHierarchyProvider",
3448
+ methods: [
3449
+ "textDocument/prepareCallHierarchy",
3450
+ "callHierarchy/incomingCalls",
3451
+ "callHierarchy/outgoingCalls"
3452
+ ],
3453
+ kind: "handler",
3454
+ value: () => true
3455
+ },
3456
+ { key: "graphProvider", methods: ["bb/graph"], kind: "handler", value: () => ({ schemaVersion: 2 }) },
3457
+ {
3458
+ key: "stalenessProvider",
3459
+ methods: ["bb/staleness"],
3460
+ kind: "handler",
3461
+ value: stalenessProviderPayload
3462
+ },
3463
+ {
3464
+ key: "impactProvider",
3465
+ methods: ["bb/impact"],
3466
+ kind: "handler",
3467
+ value: impactProviderPayload
3468
+ },
3469
+ {
3470
+ key: "workspace",
3471
+ methods: [],
3472
+ kind: "static",
3473
+ value: workspaceProviderPayload
3474
+ },
3475
+ {
3476
+ /**
3477
+ * The declared-absence channel. `kind: "static"` because it describes a
3478
+ * SERVER-TO-CLIENT notification, not a request handler: it is offered
3479
+ * whenever the server is running, and there is deliberately no inbound
3480
+ * `bb/absence` handler to advertise (G10 asserts an inbound request gets
3481
+ * MethodNotFound). `direction` is stated so a client cannot build a
3482
+ * request around it.
3483
+ */
3484
+ key: "absenceProvider",
3485
+ methods: [],
3486
+ kind: "static",
3487
+ value: absenceProviderPayload
3488
+ }
3489
+ ];
3490
+ function capabilityKeyForMethod(method) {
3491
+ return CAPABILITY_COVERAGE.find((e) => e.methods.includes(method))?.key;
3492
+ }
3493
+ var ErrorCodes = {
3494
+ ParseError: -32700,
3495
+ InvalidRequest: -32600,
3496
+ MethodNotFound: -32601,
3497
+ InvalidParams: -32602,
3498
+ InternalError: -32603,
3499
+ ServerNotInitialized: -32002
3500
+ };
3501
+ function languageForMessage(params) {
3502
+ const uri = params?.textDocument?.uri;
3503
+ if (typeof uri !== "string" || uri.length === 0) return null;
3504
+ return languageForPath(uriToPath(uri));
3505
+ }
3506
+ var ASTENGINE_UNROUTABLE_PREFIX = "Unsupported file type";
3507
+ function asDocumentUnroutable(error) {
3508
+ if (error instanceof DocumentUnroutable) return error;
3509
+ if (error instanceof Error && typeof error.message === "string" && error.message.startsWith(ASTENGINE_UNROUTABLE_PREFIX)) {
3510
+ return new DocumentUnroutable(error.message);
3511
+ }
3512
+ return null;
3513
+ }
3514
+ function guardUnroutableDocument(method, handler) {
3515
+ return async (params) => {
3516
+ const uri = params?.textDocument?.uri;
3517
+ const hasDocument = typeof uri === "string" && uri.length > 0;
3518
+ try {
3519
+ return await handler(params);
3520
+ } catch (error) {
3521
+ if (asDocumentUnroutable(error) === null) throw error;
3522
+ throw new DeclaredAbsenceThrow({
3523
+ method,
3524
+ reason: "language_not_launched",
3525
+ language: hasDocument ? languageForMessage(params) : null,
3526
+ detail: "this file's type is outside the analyzer's routed set, so it was never routed and the index was not searched",
3527
+ evidenceTier: "unsupported_language",
3528
+ searchedIndex: false,
3529
+ totalCount: null
3530
+ });
3531
+ }
3532
+ };
3533
+ }
3534
+ var DeclaredParamsError = class extends Error {
3535
+ code = ErrorCodes.InvalidParams;
3536
+ constructor(message) {
3537
+ super(message);
3538
+ this.name = "DeclaredParamsError";
3539
+ }
3540
+ };
3541
+ var ABSENCE_SCHEMA_VERSION = 1;
3542
+ var ABSENCE_METHOD = "bb/absence";
3543
+ var ABSENCE_LOG_TYPE = 3;
3544
+ var DocumentUnroutable = class extends Error {
3545
+ constructor(message) {
3546
+ super(message);
3547
+ this.name = "DocumentUnroutable";
3548
+ }
3549
+ };
3550
+ function absenceReasonFor(code) {
3551
+ switch (code) {
3552
+ case "language_outside_launch_set":
3553
+ case "file_type_unknown":
3554
+ return "language_not_launched";
3555
+ case "unsupported_language":
3556
+ return "unsupported_language";
3557
+ case "no_stored_calls":
3558
+ return "not_callable";
3559
+ default:
3560
+ return "not_indexed";
3561
+ }
3562
+ }
3563
+ var DeclaredAbsenceThrow = class extends Error {
3564
+ absence;
3565
+ constructor(absence) {
3566
+ super(absence.detail);
3567
+ this.name = "DeclaredAbsenceThrow";
3568
+ this.absence = absence;
3569
+ }
3570
+ };
3571
+ var JSONRPC = "2.0";
3572
+ function isNotification(msg) {
3573
+ return msg.method !== void 0 && msg.id === void 0;
3574
+ }
3575
+ function isResponse(msg) {
3576
+ return msg.method === void 0 && msg.id !== void 0;
3577
+ }
3578
+ var BbLspServer = class {
3579
+ lifecycleState = "uninitialized";
3580
+ handlers = /* @__PURE__ */ new Map();
3581
+ /** Methods opted into the declared-absence seam (see register). */
3582
+ absentableMethods = /* @__PURE__ */ new Set();
3583
+ /**
3584
+ * Server→client notifications produced by the most recent handleMessage
3585
+ * call, drained by the wire loop. Queueing them here (rather than writing
3586
+ * straight to the stream) keeps handleMessage pure and testable, and is what
3587
+ * lets the verifier assert on EXACTLY what a client would receive.
3588
+ */
3589
+ outboundNotifications = [];
3590
+ protocolEvents = [];
3591
+ hooks;
3592
+ stderrLogPath = process.env.BB_LSP_STDERR_LOG ?? null;
3593
+ context;
3594
+ constructor(root, hooks = {}) {
3595
+ const resolved = path2.resolve(root);
3596
+ let canonical = resolved;
3597
+ try {
3598
+ canonical = realpathSync.native(resolved);
3599
+ } catch {
3600
+ }
3601
+ this.context = {
3602
+ root: canonical,
3603
+ projectId: getProjectId(canonical),
3604
+ serverInfo: { name: "bb_lsp", version: BACKBENCHER_BRAIN_VERSION }
3605
+ };
3606
+ this.hooks = hooks;
3607
+ }
3608
+ /** Register a feature handler. Capabilities derive from this table.
3609
+ *
3610
+ * `absentable: true` opts the method into the declared-absence seam: a
3611
+ * component fault of a DECLARED type (PositionError, DeclaredAbsenceThrow,
3612
+ * DocumentUnroutable) then answers as declared absence with a reason the
3613
+ * client can read, instead of -32603. The opt-in is per method and
3614
+ * deliberate: a blanket catch over all handlers would let a genuine bug
3615
+ * render as "nothing here", and G10 gates exactly that regression. */
3616
+ register(method, handler, opts = {}) {
3617
+ this.handlers.set(method, opts.absentable ? guardUnroutableDocument(method, handler) : handler);
3618
+ if (opts.absentable) this.absentableMethods.add(method);
3619
+ }
3620
+ /** The LSP methods this instance can actually serve — the honest successor
3621
+ * to the deleted `IMPLEMENTED_METHODS` export, which was a frozen empty
3622
+ * array while twelve handlers were registered. Handler set is per-instance
3623
+ * (it is whatever `register`/`wireFeatures` put there), so the truth is an
3624
+ * instance read, not a module constant. */
3625
+ implementedMethods() {
3626
+ return [...this.handlers.keys()].sort();
3627
+ }
3628
+ /** Capabilities advertised in the initialize result — derived from the
3629
+ * registered handler set so advertisement can never outrun reality, and
3630
+ * cross-checked against CAPABILITY_COVERAGE by script/m24/verify-server.ts
3631
+ * (G6) in both directions.
3632
+ *
3633
+ * The three additions below are the ones a client could not previously
3634
+ * discover:
3635
+ * - stalenessProvider / impactProvider: bb/staleness and bb/impact were
3636
+ * registered but had NO capability key, so a client could only reach
3637
+ * them blind. LSP 3.18 has no standard `impactProvider`, and the
3638
+ * `bb/` namespace is private honesty plumbing rather than a standard
3639
+ * provider, so these ride top-level keys exactly as bb/graph already
3640
+ * did (`graphProvider`) — with the schemaVersion the handler ENFORCES
3641
+ * on inbound requests, so a client can detect a version mismatch before
3642
+ * it sends one. The G6 table makes their absence a gate failure.
3643
+ * - workspace: the single-root limitation is a server CONSTANT, not a
3644
+ * handler-derived fact, so it is declared unconditionally. Advertising
3645
+ * `workspaceFolders.supported: false` is how a client learns it from the
3646
+ * wire (contract §11.6) instead of inferring it from a hang.
3647
+ * What is deliberately NOT here: `textDocumentSync`. This server registers
3648
+ * no `textDocument/did*` handler, so advertising a sync kind would tell the
3649
+ * client to send notifications into a void — a capability we do not
3650
+ * implement, which contract §2 calls a gate failure. The sync model is
3651
+ * filesystem-side (see the file header) and is advertised, truthfully, as
3652
+ * `stalenessProvider.syncModel`. G8 pins the absence so a future edit that
3653
+ * advertises a sync kind without a did* handler fails the gate. */
3654
+ capabilities() {
3655
+ const caps = {};
3656
+ if (this.handlers.has("textDocument/hover")) caps.hoverProvider = true;
3657
+ if (this.handlers.has("textDocument/definition")) caps.definitionProvider = true;
3658
+ if (this.handlers.has("textDocument/references")) caps.referencesProvider = true;
3659
+ if (this.handlers.has("textDocument/documentHighlight")) caps.documentHighlightProvider = true;
3660
+ if (this.handlers.has("textDocument/documentSymbol")) caps.documentSymbolProvider = true;
3661
+ if (this.handlers.has("workspace/symbol")) caps.workspaceSymbolProvider = true;
3662
+ if (this.handlers.has("textDocument/prepareCallHierarchy") && this.handlers.has("callHierarchy/incomingCalls") && this.handlers.has("callHierarchy/outgoingCalls")) {
3663
+ caps.callHierarchyProvider = true;
3664
+ }
3665
+ if (this.handlers.has("bb/graph")) caps.graphProvider = { schemaVersion: 2 };
3666
+ if (this.handlers.has("bb/staleness")) caps.stalenessProvider = stalenessProviderPayload();
3667
+ if (this.handlers.has("bb/impact")) caps.impactProvider = impactProviderPayload();
3668
+ caps.absenceProvider = absenceProviderPayload();
3669
+ caps.workspace = workspaceProviderPayload();
3670
+ return caps;
3671
+ }
3672
+ state() {
3673
+ return this.lifecycleState;
3674
+ }
3675
+ /** The server→client notifications queued by the last handleMessage call.
3676
+ * Read-only: a test may inspect them, and the wire loop still drains the
3677
+ * same queue, so what the test asserts on is byte-for-byte what a client
3678
+ * receives. */
3679
+ notifications() {
3680
+ return [...this.outboundNotifications];
3681
+ }
3682
+ /** Take the queued notifications, leaving the queue empty. */
3683
+ drainNotifications() {
3684
+ const queued = this.outboundNotifications;
3685
+ this.outboundNotifications = [];
3686
+ return queued;
3687
+ }
3688
+ /** Queue a server→client notification. Every notification this server emits
3689
+ * goes through here so the carriers cannot drift: `bb/absence` for the
3690
+ * machine-readable record and `window/logMessage` for the human line are
3691
+ * emitted as ONE unit from `emitAbsence`, never separately. */
3692
+ notify(method, params) {
3693
+ this.outboundNotifications.push({ jsonrpc: JSONRPC, method, params });
3694
+ }
3695
+ /**
3696
+ * Announce a declared absence on BOTH carriers, from one call, so they cannot
3697
+ * disagree:
3698
+ * - `bb/absence` (versioned, machine-readable; what a client should read)
3699
+ * - `window/logMessage` (the protocol's own channel; reaches a client that
3700
+ * has never heard of bb/*)
3701
+ * Emitted ONLY on the absence path — never on a served request (G9's negative
3702
+ * case gates that; logging every successful hover would be noise the user
3703
+ * learns to ignore, which is how a warning channel dies).
3704
+ */
3705
+ emitAbsence(absence) {
3706
+ this.notify(ABSENCE_METHOD, { schemaVersion: ABSENCE_SCHEMA_VERSION, ...absence });
3707
+ this.notify("window/logMessage", {
3708
+ type: ABSENCE_LOG_TYPE,
3709
+ // Named by method so the log line is actionable on its own: a user who
3710
+ // only sees this in an output panel learns WHICH request was not
3711
+ // answered, and why.
3712
+ message: `bb ${absence.method}: declared absence (${absence.reason}) \u2014 ${absence.detail}`
3713
+ });
3714
+ }
3715
+ events() {
3716
+ return this.protocolEvents;
3717
+ }
3718
+ record(direction, method, errorCode) {
3719
+ const event = { direction, method, at: (/* @__PURE__ */ new Date()).toISOString() };
3720
+ if (errorCode !== void 0) event.errorCode = errorCode;
3721
+ this.protocolEvents.push(event);
3722
+ this.hooks.onEvent?.(event);
3723
+ }
3724
+ /**
3725
+ * Handle one decoded JSON-RPC message. Returns the response message to send
3726
+ * (null for notifications and responses, which produce no reply).
3727
+ */
3728
+ async handleMessage(message) {
3729
+ if (!message || typeof message !== "object") {
3730
+ return this.errorResponse(null, ErrorCodes.InvalidRequest, "invalid message");
3731
+ }
3732
+ if (isResponse(message)) return null;
3733
+ const method = message.method;
3734
+ if (typeof method !== "string") {
3735
+ return this.errorResponse(message.id ?? null, ErrorCodes.InvalidRequest, "missing method");
3736
+ }
3737
+ if (this.lifecycleState === "uninitialized" && method !== "initialize" && method !== "exit") {
3738
+ this.record("in", method, ErrorCodes.ServerNotInitialized);
3739
+ if (isNotification(message)) return null;
3740
+ return this.errorResponse(message.id, ErrorCodes.ServerNotInitialized, "server not initialized");
3741
+ }
3742
+ if (this.lifecycleState === "shutdown" && method !== "exit") {
3743
+ this.record("in", method, ErrorCodes.InvalidRequest);
3744
+ if (isNotification(message)) return null;
3745
+ return this.errorResponse(message.id, ErrorCodes.InvalidRequest, "invalid request after shutdown");
3746
+ }
3747
+ if (method === "exit") {
3748
+ this.record("in", "exit");
3749
+ return null;
3750
+ }
3751
+ if (method === "initialize") {
3752
+ this.record("in", "initialize");
3753
+ this.lifecycleState = "initialized";
3754
+ this.record("out", "initialize");
3755
+ return {
3756
+ jsonrpc: JSONRPC,
3757
+ id: message.id,
3758
+ result: {
3759
+ capabilities: this.capabilities(),
3760
+ serverInfo: this.context.serverInfo,
3761
+ protocolVersion: "3.18"
3762
+ }
3763
+ };
3764
+ }
3765
+ if (method === "initialized") {
3766
+ this.record("in", "initialized");
3767
+ return null;
3768
+ }
3769
+ if (method === "shutdown") {
3770
+ this.record("in", "shutdown");
3771
+ this.lifecycleState = "shutdown";
3772
+ this.record("out", "shutdown");
3773
+ return { jsonrpc: JSONRPC, id: message.id, result: null };
3774
+ }
3775
+ const handler = this.handlers.get(method);
3776
+ if (handler) {
3777
+ this.record("in", method);
3778
+ this.outboundNotifications = [];
3779
+ try {
3780
+ const result = await handler(message.params);
3781
+ this.record("out", method);
3782
+ return { jsonrpc: JSONRPC, id: message.id, result };
3783
+ } catch (error) {
3784
+ if (error instanceof DeclaredParamsError) {
3785
+ this.record("out", method, ErrorCodes.InvalidParams);
3786
+ return this.errorResponse(message.id, ErrorCodes.InvalidParams, error.message);
3787
+ }
3788
+ const absentable = this.absentableMethods.has(method);
3789
+ if (absentable && error instanceof DeclaredAbsenceThrow) {
3790
+ this.emitAbsence(error.absence);
3791
+ this.record("out", method);
3792
+ return { jsonrpc: JSONRPC, id: message.id, result: null };
3793
+ }
3794
+ if (absentable && error instanceof PositionError) {
3795
+ this.emitAbsence({
3796
+ method,
3797
+ reason: "unresolvable_location",
3798
+ language: languageForMessage(message.params),
3799
+ detail: `${error.message} \u2014 the position does not resolve, so there is no answer to give (contract \xA713.10)`,
3800
+ evidenceTier: null,
3801
+ searchedIndex: false,
3802
+ totalCount: null
3803
+ });
3804
+ this.record("out", method);
3805
+ return { jsonrpc: JSONRPC, id: message.id, result: null };
3806
+ }
3807
+ if (absentable && error instanceof DocumentUnroutable) {
3808
+ this.emitAbsence({
3809
+ method,
3810
+ reason: "language_not_launched",
3811
+ language: null,
3812
+ detail: `${error.message} \u2014 this file type is outside the analyzer's routed set, so the index was never searched`,
3813
+ evidenceTier: null,
3814
+ searchedIndex: false,
3815
+ totalCount: null
3816
+ });
3817
+ this.record("out", method);
3818
+ return { jsonrpc: JSONRPC, id: message.id, result: null };
3819
+ }
3820
+ this.record("out", method, ErrorCodes.InternalError);
3821
+ return this.errorResponse(message.id, ErrorCodes.InternalError, error?.message ?? String(error));
3822
+ }
3823
+ }
3824
+ this.record("in", method, ErrorCodes.MethodNotFound);
3825
+ if (isNotification(message)) return null;
3826
+ return this.errorResponse(message.id, ErrorCodes.MethodNotFound, `method not implemented: ${method}`);
3827
+ }
3828
+ /**
3829
+ * Wire loop: read Content-Length-framed JSON-RPC messages from stdin, feed
3830
+ * each to handleMessage, and write responses to stdout. Resolves when the
3831
+ * exit notification arrives (or stdin closes).
3832
+ */
3833
+ async run(input = process.stdin, output = process.stdout) {
3834
+ const onEpipe = (err) => {
3835
+ if (err?.code === "EPIPE") {
3836
+ try {
3837
+ output.end();
3838
+ } catch {
3839
+ }
3840
+ process.exit(0);
3841
+ }
3842
+ };
3843
+ process.on("error", onEpipe);
3844
+ if (output === process.stdout) process.stdout.on("error", onEpipe);
3845
+ if (input === process.stdin) process.stdin.on("error", onEpipe);
3846
+ const writeMessage = (message) => {
3847
+ if (!message) return;
3848
+ const body = Buffer.from(JSON.stringify(message), "utf8");
3849
+ output.write(`Content-Length: ${body.length}\r
3850
+ \r
3851
+ `);
3852
+ output.write(body);
3853
+ };
3854
+ const writeNotification = (notification) => {
3855
+ const body = Buffer.from(JSON.stringify(notification), "utf8");
3856
+ output.write(`Content-Length: ${body.length}\r
3857
+ \r
3858
+ `);
3859
+ output.write(body);
3860
+ };
3861
+ const drainNotifications = () => {
3862
+ for (const notification of this.drainNotifications()) writeNotification(notification);
3863
+ };
3864
+ return new Promise((resolve) => {
3865
+ let finished = false;
3866
+ const finish = () => {
3867
+ if (!finished) {
3868
+ finished = true;
3869
+ resolve();
3870
+ }
3871
+ };
3872
+ let chunks = [];
3873
+ let buffered = 0;
3874
+ let contentLength = -1;
3875
+ const tryExtract = () => {
3876
+ while (true) {
3877
+ if (contentLength < 0) {
3878
+ const combined = Buffer.concat(chunks);
3879
+ const idx = combined.indexOf("\r\n\r\n");
3880
+ if (idx === -1) return;
3881
+ const headerText = combined.subarray(0, idx).toString("utf8");
3882
+ const match = /Content-Length:\s*(\d+)\s*(?:\r\n.*)?$/is.exec(headerText);
3883
+ if (!match) {
3884
+ writeMessage(this.errorResponse(null, ErrorCodes.ParseError, "missing Content-Length header"));
3885
+ chunks = [];
3886
+ buffered = 0;
3887
+ return;
3888
+ }
3889
+ contentLength = parseInt(match[1], 10);
3890
+ const rest = combined.subarray(idx + 4);
3891
+ chunks = [rest];
3892
+ buffered = rest.length;
3893
+ }
3894
+ if (contentLength >= 0 && buffered >= contentLength) {
3895
+ const combined = Buffer.concat(chunks);
3896
+ const body = combined.subarray(0, contentLength);
3897
+ chunks = [combined.subarray(contentLength)];
3898
+ buffered = chunks[0].length;
3899
+ contentLength = -1;
3900
+ void this.dispatch(body.toString("utf8"), writeMessage, finish, drainNotifications);
3901
+ if (finished) return;
3902
+ continue;
3903
+ }
3904
+ return;
3905
+ }
3906
+ };
3907
+ input.on("data", (chunk) => {
3908
+ chunks.push(chunk);
3909
+ buffered += chunk.length;
3910
+ tryExtract();
3911
+ });
3912
+ input.on("end", finish);
3913
+ input.on("error", finish);
3914
+ });
3915
+ }
3916
+ async dispatch(body, write, finish, drainNotifications) {
3917
+ let message;
3918
+ try {
3919
+ message = JSON.parse(body);
3920
+ } catch {
3921
+ write(this.errorResponse(null, ErrorCodes.ParseError, "parse error"));
3922
+ return;
3923
+ }
3924
+ if (message && isNotification(message) && message.method === "exit") {
3925
+ this.record("in", "exit");
3926
+ finish();
3927
+ return;
3928
+ }
3929
+ const response = await this.handleMessage(message);
3930
+ if (this.stderrLogPath) {
3931
+ const req = message;
3932
+ const err = response?.error;
3933
+ try {
3934
+ appendFileSync(this.stderrLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${req.method ?? "?"} id=${String(req.id)} params=${JSON.stringify(req.params ?? null)?.slice(0, 220)} -> ${err ? "ERROR " + String(err.message).slice(0, 200) : "ok"}
3935
+ `);
3936
+ } catch {
3937
+ }
3938
+ }
3939
+ write(response);
3940
+ drainNotifications();
3941
+ }
3942
+ errorResponse(id, code, message) {
3943
+ return {
3944
+ jsonrpc: JSONRPC,
3945
+ id: id ?? null,
3946
+ error: { code, message }
3947
+ };
3948
+ }
3949
+ };
3950
+ function createServer(root, hooks = {}) {
3951
+ return new BbLspServer(root, hooks);
3952
+ }
3953
+ function uriToPath(uri) {
3954
+ if (uri.startsWith("file://")) {
3955
+ try {
3956
+ return fileURLToPath(uri);
3957
+ } catch {
3958
+ return uri.slice("file://".length);
3959
+ }
3960
+ }
3961
+ return uri;
3962
+ }
3963
+ function wireFeatures(server) {
3964
+ const projectId = server.context.projectId;
3965
+ let monitor = null;
3966
+ async function freshnessMonitor() {
3967
+ if (monitor !== null) return monitor;
3968
+ const { createFreshnessMonitor } = await import("../freshness-3K6VU5OT.js");
3969
+ const { reindexFilesParallel } = await import("../parallel-indexer-FK2XKZXY.js");
3970
+ const raw = await BrainDatabase.getRawInstance(projectId);
3971
+ monitor = createFreshnessMonitor({
3972
+ projectId,
3973
+ projectPath: server.context.root,
3974
+ raw,
3975
+ fileIdForPath: (p) => {
3976
+ try {
3977
+ const row = raw.prepare("SELECT id FROM brain_file WHERE path = ?").get(p);
3978
+ return row?.id ?? null;
3979
+ } catch {
3980
+ return null;
3981
+ }
3982
+ },
3983
+ // LSP-safety-critical: the indexer's DEFAULT logger writes progress to
3984
+ // process.stdout — protocol poison on a stdio server (progress lines
3985
+ // corrupt JSON-RPC framing; the editor client dies with "Header must
3986
+ // provide a Content-Length property", found live by E5). All server-side
3987
+ // indexer output must go to stderr; worker forks already do the same.
3988
+ reindex: (changes) => reindexFilesParallel(projectId, server.context.root, changes, {
3989
+ logger: { log: (...args) => console.error(...args), write: (s) => process.stderr.write(s) }
3990
+ }),
3991
+ log: (msg, meta) => console.error(`[bb_lsp] ${msg}`, meta ?? "")
3992
+ });
3993
+ monitor.start();
3994
+ return monitor;
3995
+ }
3996
+ server.register("bb/staleness", async (params) => {
3997
+ const mon = await freshnessMonitor();
3998
+ const filePath = uriToPath(params?.uri ?? "");
3999
+ let fileId = null;
4000
+ try {
4001
+ const raw = await BrainDatabase.getRawInstance(projectId);
4002
+ const row = raw.prepare("SELECT id FROM brain_file WHERE path = ?").get(filePath);
4003
+ fileId = row?.id ?? null;
4004
+ } catch {
4005
+ }
4006
+ if (fileId === null) {
4007
+ return { stale: true, status: "missing", pendingReindex: false };
4008
+ }
4009
+ return mon.marker(fileId);
4010
+ });
4011
+ server.register("bb/graph", async (params) => {
4012
+ const { handleGraphRequest } = await import("../graph_request-Q6NKB6N2.js");
4013
+ let raw = null;
4014
+ try {
4015
+ raw = await BrainDatabase.getRawInstance(projectId);
4016
+ } catch {
4017
+ }
4018
+ return handleGraphRequest(raw, projectId, params);
4019
+ });
4020
+ server.register("bb/impact", async (params) => {
4021
+ const { handleImpactRequest } = await import("../impact_request-LYP672WE.js");
4022
+ let raw = null;
4023
+ try {
4024
+ raw = await BrainDatabase.getRawInstance(projectId);
4025
+ } catch {
4026
+ }
4027
+ return handleImpactRequest(raw, projectId, params);
4028
+ });
4029
+ server.register("textDocument/hover", async (params) => {
4030
+ const raw = await BrainDatabase.getRawInstance(projectId);
4031
+ const r = await hover(raw, {
4032
+ filePath: uriToPath(params?.textDocument?.uri ?? ""),
4033
+ position: params?.position ?? { line: 0, character: 0 }
4034
+ });
4035
+ if (r.contents === null || r.range === null || r.data === null) {
4036
+ const limits = r.provenance?.limitations ?? [];
4037
+ const notLaunched = limits.some((l) => l.includes("launch language set"));
4038
+ throw new DeclaredAbsenceThrow({
4039
+ method: "textDocument/hover",
4040
+ reason: notLaunched ? "language_not_launched" : "not_indexed",
4041
+ language: languageForMessage(params),
4042
+ // Human prose, never a JSON blob (§15.4): the component's own
4043
+ // limitation sentences, joined.
4044
+ detail: limits.length > 0 ? limits.join("; ") : "no indexed symbol at this position (the index was searched and stores nothing here)",
4045
+ evidenceTier: r.provenance?.evidenceClass ?? null,
4046
+ // A launch-language miss means the index WAS asked. A non-launch file
4047
+ // means it never was — the distinction that lets a client label the
4048
+ // difference instead of guessing.
4049
+ searchedIndex: !notLaunched,
4050
+ totalCount: null
4051
+ });
4052
+ }
4053
+ return {
4054
+ contents: r.contents,
4055
+ range: { start: r.range.start, end: r.range.end },
4056
+ data: { provenance: r.provenance, symbol: r.data }
4057
+ };
4058
+ }, { absentable: true });
4059
+ server.register("textDocument/definition", async (params) => {
4060
+ const raw = await BrainDatabase.getRawInstance(projectId);
4061
+ const r = await resolveDefinition(raw, {
4062
+ filePath: uriToPath(params?.textDocument?.uri ?? ""),
4063
+ line: params?.position?.line ?? 0,
4064
+ character: params?.position?.character ?? 0
4065
+ });
4066
+ if (!r.found || r.location === null) {
4067
+ throw new DeclaredAbsenceThrow(
4068
+ navigationAbsence("textDocument/definition", params, r.evidenceTier, {
4069
+ limit: r.symbol?.qualifiedName ?? r.symbol?.name ?? null
4070
+ })
4071
+ );
4072
+ }
4073
+ return navLocationToWire(r.location);
4074
+ }, { absentable: true });
4075
+ server.register("textDocument/documentHighlight", async (params) => {
4076
+ const raw = await BrainDatabase.getRawInstance(projectId);
4077
+ const r = await documentHighlight(raw, {
4078
+ filePath: uriToPath(params?.textDocument?.uri ?? ""),
4079
+ line: params?.position?.line ?? 0,
4080
+ character: params?.position?.character ?? 0
4081
+ });
4082
+ if (r.highlights.length === 0) {
4083
+ const tier = r.evidenceTier;
4084
+ const notLaunched = tier === "unsupported_language";
4085
+ throw new DeclaredAbsenceThrow({
4086
+ method: "textDocument/documentHighlight",
4087
+ reason: notLaunched ? "language_not_launched" : "not_indexed",
4088
+ language: languageForMessage(params),
4089
+ detail: notLaunched ? "this file's type is outside the analyzer's routed set, so it was never routed and the index was not searched" : "the index was searched and stores no occurrence of this symbol at the requested position",
4090
+ evidenceTier: tier,
4091
+ searchedIndex: !notLaunched,
4092
+ totalCount: 0
4093
+ });
4094
+ }
4095
+ return r.highlights.map((h) => ({
4096
+ range: { start: h.start, end: h.end },
4097
+ kind: h.kind,
4098
+ data: { evidenceTier: r.evidenceTier, staleness: r.staleness, symbol: r.symbol }
4099
+ }));
4100
+ }, { absentable: true });
4101
+ server.register("textDocument/references", async (params) => {
4102
+ const raw = await BrainDatabase.getRawInstance(projectId);
4103
+ const r = await resolveReferences(raw, {
4104
+ filePath: uriToPath(params?.textDocument?.uri ?? ""),
4105
+ line: params?.position?.line ?? 0,
4106
+ character: params?.position?.character ?? 0,
4107
+ includeDeclaration: params?.context?.includeDeclaration ?? false
4108
+ });
4109
+ if (r.locations.length === 0) {
4110
+ throw new DeclaredAbsenceThrow(navigationAbsence("textDocument/references", params, r.evidenceTier));
4111
+ }
4112
+ return r.locations.map(navLocationToWire);
4113
+ }, { absentable: true });
4114
+ server.register("textDocument/documentSymbol", async (params) => {
4115
+ const raw = await BrainDatabase.getRawInstance(projectId);
4116
+ const r = documentSymbols(raw, uriToPath(params?.textDocument?.uri ?? ""));
4117
+ const tier = r.evidenceTier;
4118
+ const notIndexed = r.notIndexed;
4119
+ const toWire = (e) => ({
4120
+ name: e.name,
4121
+ detail: e.qualifiedName ?? void 0,
4122
+ kind: symbolKindFor(e.symbolType),
4123
+ range: symbolRangeToWire(e.range),
4124
+ selectionRange: symbolRangeToWire(e.selectionRange),
4125
+ children: e.children?.map(toWire),
4126
+ data: { id: e.id, evidenceTier: tier, notIndexed }
4127
+ });
4128
+ return r.symbols.map(toWire);
4129
+ });
4130
+ server.register("workspace/symbol", async (params) => {
4131
+ const raw = await BrainDatabase.getRawInstance(projectId);
4132
+ const r = workspaceSymbols(raw, {
4133
+ query: params?.query ?? "",
4134
+ limit: params?.limit
4135
+ });
4136
+ return r.symbols.map((s) => ({
4137
+ name: s.name,
4138
+ kind: symbolKindFor(s.symbolType),
4139
+ containerName: s.qualifiedName ?? void 0,
4140
+ location: {
4141
+ uri: `file://${s.path}`,
4142
+ range: {
4143
+ start: { line: s.line, character: 0 },
4144
+ end: { line: s.line, character: 0 }
4145
+ }
4146
+ }
4147
+ }));
4148
+ });
4149
+ server.register("textDocument/prepareCallHierarchy", async (params) => {
4150
+ const p = requireObject(params, "params");
4151
+ const doc = requireObject(p.textDocument, "params.textDocument");
4152
+ if (typeof doc.uri !== "string" || doc.uri.length === 0) {
4153
+ throw new DeclaredParamsError("bb/callHierarchy: params.textDocument.uri must be a non-empty string");
4154
+ }
4155
+ const pos = requireObject(p.position, "params.position");
4156
+ if (typeof pos.line !== "number" || typeof pos.character !== "number") {
4157
+ throw new DeclaredParamsError("bb/callHierarchy: params.position must carry numeric line and character");
4158
+ }
4159
+ const raw = await BrainDatabase.getRawInstance(projectId);
4160
+ const r = await prepareCallHierarchy(raw, {
4161
+ filePath: uriToPath(doc.uri),
4162
+ line: pos.line,
4163
+ character: pos.character
4164
+ });
4165
+ if (r.item === null && r.absence) {
4166
+ const code = r.absence.code;
4167
+ throw new DeclaredAbsenceThrow({
4168
+ method: "textDocument/prepareCallHierarchy",
4169
+ reason: absenceReasonFor(code),
4170
+ language: r.absence.language ?? null,
4171
+ detail: r.absence.message,
4172
+ evidenceTier: null,
4173
+ searchedIndex: code !== "language_outside_launch_set" && code !== "file_type_unknown",
4174
+ totalCount: null
4175
+ });
4176
+ }
4177
+ return r.item === null ? null : [r.item];
4178
+ }, { absentable: true });
4179
+ server.register("callHierarchy/incomingCalls", async (params) => {
4180
+ const raw = await BrainDatabase.getRawInstance(projectId);
4181
+ const r = await expandCalls(raw, projectId, {
4182
+ item: requireCallHierarchyItem(params),
4183
+ direction: "incoming"
4184
+ });
4185
+ return r.calls;
4186
+ });
4187
+ server.register("callHierarchy/outgoingCalls", async (params) => {
4188
+ const raw = await BrainDatabase.getRawInstance(projectId);
4189
+ const r = await expandCalls(raw, projectId, {
4190
+ item: requireCallHierarchyItem(params),
4191
+ direction: "outgoing"
4192
+ });
4193
+ return r.calls;
4194
+ });
4195
+ }
4196
+ function requireObject(value, what, shape) {
4197
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
4198
+ throw new DeclaredParamsError(
4199
+ `bb/callHierarchy: ${what} must be an object${shape === void 0 ? "" : ` carrying { ${shape} }`}`
4200
+ );
4201
+ }
4202
+ return value;
4203
+ }
4204
+ function requireCallHierarchyItem(params) {
4205
+ const p = requireObject(params, "params");
4206
+ const item = requireObject(p.item, "params.item", "symbolId: string, isExternal: boolean");
4207
+ const data = requireObject(item.data, "params.item.data", "symbolId: string, isExternal: boolean");
4208
+ if (typeof data.symbolId !== "string" || data.symbolId.length === 0) {
4209
+ throw new DeclaredParamsError("bb/callHierarchy: params.item.data.symbolId must be a non-empty string");
4210
+ }
4211
+ if (typeof data.isExternal !== "boolean") {
4212
+ throw new DeclaredParamsError("bb/callHierarchy: params.item.data.isExternal must be a boolean");
4213
+ }
4214
+ return item;
4215
+ }
4216
+ function navigationAbsence(method, params, evidenceTier, extra = {}) {
4217
+ const language = languageForMessage(params);
4218
+ const notLaunched = language === null || !isLaunchLanguage(language);
4219
+ const what = extra.limit ? ` (asked for ${extra.limit})` : "";
4220
+ const which = language === null ? "this file's type is not routed by any analyzer" : `${language} is outside the launch-language set`;
4221
+ return {
4222
+ method,
4223
+ reason: notLaunched ? "language_not_launched" : "not_indexed",
4224
+ language,
4225
+ detail: notLaunched ? `${which}, so the index was never searched for this position${what}` : `the index was searched and stores no matching symbol at this position${what}`,
4226
+ evidenceTier,
4227
+ searchedIndex: !notLaunched,
4228
+ totalCount: null
4229
+ };
4230
+ }
4231
+ function navLocationToWire(loc) {
4232
+ return {
4233
+ uri: `file://${loc.path}`,
4234
+ range: {
4235
+ start: { line: loc.line, character: loc.character },
4236
+ end: { line: loc.endLine, character: loc.endCharacter }
4237
+ }
4238
+ };
4239
+ }
4240
+ function symbolRangeToWire(r) {
4241
+ return {
4242
+ start: { line: r.line, character: r.character },
4243
+ end: { line: r.endLine, character: r.endCharacter }
4244
+ };
4245
+ }
4246
+ var isMain = typeof globalThis.Bun !== "undefined" ? import.meta.main === true : process.argv[1] !== void 0 && // Windows argv[1] uses backslashes (…\dist\lsp\server.js) — normalize
4247
+ // so the entry check is separator-independent (found by the E7 win32
4248
+ // CI leg: the server silently exited 0 without starting, 2026-09-05).
4249
+ (process.argv[1].replace(/\\/g, "/").endsWith("lsp/server.js") || process.argv[1].replace(/\\/g, "/").endsWith("lsp/server.ts"));
4250
+ if (isMain) {
4251
+ const dirIdx = process.argv.indexOf("--dir");
4252
+ const root = dirIdx !== -1 ? process.argv[dirIdx + 1] : process.cwd();
4253
+ if (process.env.BB_LSP_STDERR_LOG) {
4254
+ const trace = (what, err) => {
4255
+ try {
4256
+ appendFileSync(process.env.BB_LSP_STDERR_LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${what}: ${err?.stack ?? String(err)}
4257
+ `);
4258
+ } catch {
4259
+ }
4260
+ };
4261
+ process.on("uncaughtException", (e) => trace("uncaughtException", e));
4262
+ process.on("unhandledRejection", (e) => trace("unhandledRejection", e));
4263
+ }
4264
+ const server = createServer(root);
4265
+ wireFeatures(server);
4266
+ server.run().catch((error) => {
4267
+ console.error("bb_lsp failed:", error?.message ?? String(error));
4268
+ if (process.env.BB_LSP_STDERR_LOG) {
4269
+ try {
4270
+ appendFileSync(process.env.BB_LSP_STDERR_LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] run-failed: ${error?.stack ?? String(error)}
4271
+ `);
4272
+ } catch {
4273
+ }
4274
+ }
4275
+ process.exit(1);
4276
+ });
4277
+ }
4278
+ export {
4279
+ ABSENCE_LOG_TYPE,
4280
+ ABSENCE_METHOD,
4281
+ ABSENCE_SCHEMA_VERSION,
4282
+ BbLspServer,
4283
+ CAPABILITY_COVERAGE,
4284
+ DeclaredAbsenceThrow,
4285
+ DeclaredParamsError,
4286
+ DocumentUnroutable,
4287
+ ErrorCodes,
4288
+ LAUNCH_LANGUAGES,
4289
+ STALENESS_SCHEMA_VERSION,
4290
+ absenceProviderPayload,
4291
+ asDocumentUnroutable,
4292
+ capabilityKeyForMethod,
4293
+ createServer,
4294
+ impactProviderPayload,
4295
+ isLaunchLanguage,
4296
+ languageForPath,
4297
+ stalenessProviderPayload,
4298
+ uriToPath,
4299
+ wireFeatures,
4300
+ workspaceProviderPayload
4301
+ };