@backbencher/brain 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/EMBEDDINGS.md +355 -0
  3. package/LICENSE +21 -0
  4. package/README.md +415 -2
  5. package/bin/bb +13 -0
  6. package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
  7. package/dist/brain-index-health-KY7K552E.js +17 -0
  8. package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-2BSKD7O3.js +40 -0
  11. package/dist/chunk-2DE27DVC.js +1314 -0
  12. package/dist/chunk-2RRBMNBQ.js +322 -0
  13. package/dist/chunk-2RTN2DFU.js +711 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3LFTSMIV.js +174 -0
  17. package/dist/chunk-47HKX4AJ.js +930 -0
  18. package/dist/chunk-4H22TLOT.js +543 -0
  19. package/dist/chunk-5SD45FLC.js +818 -0
  20. package/dist/chunk-66A5ZFYA.js +1758 -0
  21. package/dist/chunk-6AIRWAO2.js +247 -0
  22. package/dist/chunk-6WNGD3NN.js +196 -0
  23. package/dist/chunk-7AN7U5IZ.js +371 -0
  24. package/dist/chunk-BHB7BYYI.js +626 -0
  25. package/dist/chunk-BQ3OVQ3P.js +125 -0
  26. package/dist/chunk-CW4XXSNN.js +1479 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DYM4FQEE.js +90 -0
  30. package/dist/chunk-EWMWK7WN.js +75 -0
  31. package/dist/chunk-FVVQTSIM.js +3427 -0
  32. package/dist/chunk-FWSL4VG6.js +1290 -0
  33. package/dist/chunk-GBI7V7KY.js +69 -0
  34. package/dist/chunk-GEFFO6O2.js +152 -0
  35. package/dist/chunk-HOWAQXJO.js +236 -0
  36. package/dist/chunk-HQSPJDHI.js +613 -0
  37. package/dist/chunk-IFZNR5UJ.js +2863 -0
  38. package/dist/chunk-J4PMBXQQ.js +336 -0
  39. package/dist/chunk-KAET47ZL.js +125 -0
  40. package/dist/chunk-KCDECRJY.js +440 -0
  41. package/dist/chunk-KLK3PBTO.js +1119 -0
  42. package/dist/chunk-L5TDYKYK.js +76 -0
  43. package/dist/chunk-MLKGABMK.js +9 -0
  44. package/dist/chunk-OWISBAOF.js +61 -0
  45. package/dist/chunk-PJTNGD3Z.js +6924 -0
  46. package/dist/chunk-QLK6ED4Q.js +235 -0
  47. package/dist/chunk-R2GW4F6R.js +19 -0
  48. package/dist/chunk-RCMTFSEF.js +87 -0
  49. package/dist/chunk-RH7KC5HU.js +12050 -0
  50. package/dist/chunk-RM3PY6TG.js +376 -0
  51. package/dist/chunk-ROMM6KMC.js +433 -0
  52. package/dist/chunk-SK6YVHFZ.js +211 -0
  53. package/dist/chunk-SM7RFQRO.js +214 -0
  54. package/dist/chunk-UFE77L67.js +782 -0
  55. package/dist/chunk-UGR4QIGS.js +67 -0
  56. package/dist/chunk-UUNHHX4M.js +13 -0
  57. package/dist/chunk-V44PGPA6.js +2104 -0
  58. package/dist/chunk-VL52PL2T.js +71 -0
  59. package/dist/chunk-VURYFIHW.js +201 -0
  60. package/dist/chunk-XJKJYIPP.js +7436 -0
  61. package/dist/chunk-YI6FF2ZR.js +340 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-642F7E5V.js +593 -0
  65. package/dist/database-TCTZDLE5.js +9 -0
  66. package/dist/doctor-GXCKSJOX.js +395 -0
  67. package/dist/edit-engine-KM7MPFEC.js +51 -0
  68. package/dist/engine-42TZFNIZ.js +11 -0
  69. package/dist/engine-LO6L4JLQ.js +18 -0
  70. package/dist/fingerprint-XDXFRWO6.js +33 -0
  71. package/dist/framework-intelligence-DWFCKV44.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-PL5YA7BA.js +21 -0
  74. package/dist/graph_request-MM4REP6H.js +350 -0
  75. package/dist/impact_request-WDE47IZ5.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-6BD7UBHR.js +34 -0
  78. package/dist/index.js +159 -0
  79. package/dist/instance-FO6QZRLH.js +19 -0
  80. package/dist/language-detection-DFCAU5DZ.js +11 -0
  81. package/dist/lsp/server.js +4301 -0
  82. package/dist/mcp/server.js +132 -0
  83. package/dist/onboard-DIQZV2NW.js +161 -0
  84. package/dist/override-companion-pass-XR7MHTP7.js +117 -0
  85. package/dist/parallel-indexer-4PW3AFXP.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-ZS5TS2Z6.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-DGZBIZS7.js +355 -0
  90. package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
  91. package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
  92. package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
  93. package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
  94. package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
  95. package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
  96. package/dist/real-css-analyzer-HPU277Q6.js +15 -0
  97. package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
  98. package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
  99. package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
  100. package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
  101. package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
  102. package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
  103. package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
  104. package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
  105. package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
  106. package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
  107. package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
  108. package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
  109. package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
  110. package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
  111. package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
  112. package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
  113. package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
  114. package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
  115. package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
  116. package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
  117. package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
  118. package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
  119. package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
  120. package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
  121. package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
  122. package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
  123. package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
  124. package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
  125. package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
  126. package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
  127. package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
  128. package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
  129. package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
  130. package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
  131. package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
  132. package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
  133. package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
  134. package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
  135. package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
  136. package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
  137. package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
  138. package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
  139. package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
  140. package/dist/regex-search-OPTLKUN5.js +239 -0
  141. package/dist/rules-I453JTRC.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-Y5GB5KXA.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-KB4MU4SI.js +16 -0
  146. package/dist/taint-YOTN5X55.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-G4NAGJIH.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-RF675A3C.js +32 -0
  151. package/dist/type-inference-4Y7WTMFE.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-LXGNFO37.js +9 -0
  154. package/dist/wasm-NMWO3WXY.js +16 -0
  155. package/docs/EMBEDDINGS.md +132 -0
  156. package/docs/README.md +178 -0
  157. package/docs/data/m10-baseline.json +602 -0
  158. package/docs/data/m10-benchmark-baseline.json +57 -0
  159. package/docs/data/m10-benchmark-last-run.json +127 -0
  160. package/docs/data/m10-final-audit.json +215 -0
  161. package/docs/data/m12-final-audit.json +138 -0
  162. package/docs/data/m12-surface-matrix.json +50 -0
  163. package/docs/data/public-tools.json +1902 -0
  164. package/docs/indexing/DEAD-ASSETS.md +88 -0
  165. package/docs/indexing/INDEXER-REVIEW.md +634 -0
  166. package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
  167. package/docs/indexing/README.md +427 -0
  168. package/docs/indexing/discovery-matrix.md +41 -0
  169. package/docs/indexing/embeddings.md +325 -0
  170. package/docs/indexing/environment-variables.md +77 -0
  171. package/docs/indexing/performance.md +323 -0
  172. package/docs/indexing/resume.md +345 -0
  173. package/docs/indexing/troubleshooting.md +391 -0
  174. package/docs/man/bb.1 +190 -0
  175. package/docs/tools/bb_analyze.md +147 -0
  176. package/docs/tools/bb_annotate.md +110 -0
  177. package/docs/tools/bb_annotations.md +106 -0
  178. package/docs/tools/bb_ask.md +101 -0
  179. package/docs/tools/bb_cross_language.md +103 -0
  180. package/docs/tools/bb_doc.md +169 -0
  181. package/docs/tools/bb_edit.md +185 -0
  182. package/docs/tools/bb_graph.md +169 -0
  183. package/docs/tools/bb_health.md +156 -0
  184. package/docs/tools/bb_impact.md +117 -0
  185. package/docs/tools/bb_index.md +108 -0
  186. package/docs/tools/bb_migrate.md +135 -0
  187. package/docs/tools/bb_patterns.md +140 -0
  188. package/docs/tools/bb_search.md +150 -0
  189. package/docs/tools/bb_security.md +100 -0
  190. package/docs/tools/bb_visualize.md +184 -0
  191. package/grammars/README.md +57 -0
  192. package/grammars/THIRD-PARTY-NOTICES.md +50 -0
  193. package/grammars/tree-sitter-c/LICENSE +21 -0
  194. package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
  195. package/grammars/tree-sitter-cpp/LICENSE +21 -0
  196. package/grammars/tree-sitter-csharp/LICENSE +21 -0
  197. package/grammars/tree-sitter-css/LICENSE +21 -0
  198. package/grammars/tree-sitter-css/README.md +21 -0
  199. package/grammars/tree-sitter-dart/LICENSE +20 -0
  200. package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
  201. package/grammars/tree-sitter-dart-upstream/README.md +18 -0
  202. package/grammars/tree-sitter-elixir/LICENSE +176 -0
  203. package/grammars/tree-sitter-elixir/README.md +11 -0
  204. package/grammars/tree-sitter-fsharp/LICENSE +21 -0
  205. package/grammars/tree-sitter-fsharp/README.md +45 -0
  206. package/grammars/tree-sitter-graphql/LICENSE +21 -0
  207. package/grammars/tree-sitter-graphql/README.md +106 -0
  208. package/grammars/tree-sitter-groovy/LICENSE +21 -0
  209. package/grammars/tree-sitter-groovy/README.md +4 -0
  210. package/grammars/tree-sitter-haskell/LICENSE +21 -0
  211. package/grammars/tree-sitter-haskell/README.md +394 -0
  212. package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
  213. package/grammars/tree-sitter-hcl/LICENSE +201 -0
  214. package/grammars/tree-sitter-hcl/README.md +39 -0
  215. package/grammars/tree-sitter-html/LICENSE +21 -0
  216. package/grammars/tree-sitter-html/README.md +21 -0
  217. package/grammars/tree-sitter-julia/LICENSE +21 -0
  218. package/grammars/tree-sitter-julia/README.md +24 -0
  219. package/grammars/tree-sitter-kotlin/LICENSE +9 -0
  220. package/grammars/tree-sitter-lua/LICENSE.md +21 -0
  221. package/grammars/tree-sitter-lua/README.md +29 -0
  222. package/grammars/tree-sitter-make/LICENSE +21 -0
  223. package/grammars/tree-sitter-matlab/LICENSE +19 -0
  224. package/grammars/tree-sitter-matlab/README.md +99 -0
  225. package/grammars/tree-sitter-nim/LICENSE +373 -0
  226. package/grammars/tree-sitter-nim/README.md +46 -0
  227. package/grammars/tree-sitter-objc/LICENSE +21 -0
  228. package/grammars/tree-sitter-objc/README.md +20 -0
  229. package/grammars/tree-sitter-perl/LICENSE +7 -0
  230. package/grammars/tree-sitter-perl/README.md +100 -0
  231. package/grammars/tree-sitter-powershell/LICENSE +21 -0
  232. package/grammars/tree-sitter-powershell/README.md +7 -0
  233. package/grammars/tree-sitter-proto/LICENSE +21 -0
  234. package/grammars/tree-sitter-proto/README.md +105 -0
  235. package/grammars/tree-sitter-r/LICENSE +9 -0
  236. package/grammars/tree-sitter-r/README.md +66 -0
  237. package/grammars/tree-sitter-ruby/LICENSE +22 -0
  238. package/grammars/tree-sitter-scss/LICENSE +21 -0
  239. package/grammars/tree-sitter-scss/README.md +17 -0
  240. package/grammars/tree-sitter-solidity/LICENSE +7 -0
  241. package/grammars/tree-sitter-sql/LICENSE +21 -0
  242. package/grammars/tree-sitter-typescript/LICENSE +21 -0
  243. package/grammars/tree-sitter-vba/LICENSE +21 -0
  244. package/grammars/tree-sitter-vba/README.md +738 -0
  245. package/grammars/tree-sitter-vbnet/LICENSE +21 -0
  246. package/grammars/tree-sitter-vbnet/README.md +322 -0
  247. package/grammars/tree-sitter-xml/LICENSE +19 -0
  248. package/grammars/tree-sitter-yaml/LICENSE +20 -0
  249. package/grammars/tree-sitter-yaml/README.md +21 -0
  250. package/package.json +184 -4
  251. package/wasm/dart.wasm +0 -0
  252. package/wasm/tree-sitter-bash.wasm +0 -0
  253. package/wasm/tree-sitter-c.wasm +0 -0
  254. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  255. package/wasm/tree-sitter-clean.wasm +0 -0
  256. package/wasm/tree-sitter-clojure.wasm +0 -0
  257. package/wasm/tree-sitter-core_schema.wasm +0 -0
  258. package/wasm/tree-sitter-cpp.wasm +0 -0
  259. package/wasm/tree-sitter-css.wasm +0 -0
  260. package/wasm/tree-sitter-dart.wasm +0 -0
  261. package/wasm/tree-sitter-elisp.wasm +0 -0
  262. package/wasm/tree-sitter-elixir.wasm +0 -0
  263. package/wasm/tree-sitter-elm.wasm +0 -0
  264. package/wasm/tree-sitter-embedded_template.wasm +0 -0
  265. package/wasm/tree-sitter-erlang.wasm +0 -0
  266. package/wasm/tree-sitter-fsharp.wasm +0 -0
  267. package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
  268. package/wasm/tree-sitter-go.wasm +0 -0
  269. package/wasm/tree-sitter-graphql.wasm +0 -0
  270. package/wasm/tree-sitter-groovy.wasm +0 -0
  271. package/wasm/tree-sitter-haskell.wasm +0 -0
  272. package/wasm/tree-sitter-hcl.wasm +0 -0
  273. package/wasm/tree-sitter-html.wasm +0 -0
  274. package/wasm/tree-sitter-java.wasm +0 -0
  275. package/wasm/tree-sitter-javascript.wasm +0 -0
  276. package/wasm/tree-sitter-json.wasm +0 -0
  277. package/wasm/tree-sitter-json_schema.wasm +0 -0
  278. package/wasm/tree-sitter-julia.wasm +0 -0
  279. package/wasm/tree-sitter-kotlin.wasm +0 -0
  280. package/wasm/tree-sitter-lua.wasm +0 -0
  281. package/wasm/tree-sitter-make.wasm +0 -0
  282. package/wasm/tree-sitter-markdown.wasm +0 -0
  283. package/wasm/tree-sitter-markdown_inline.wasm +0 -0
  284. package/wasm/tree-sitter-matlab.wasm +0 -0
  285. package/wasm/tree-sitter-nim.wasm +0 -0
  286. package/wasm/tree-sitter-objc.wasm +0 -0
  287. package/wasm/tree-sitter-ocaml.wasm +0 -0
  288. package/wasm/tree-sitter-perl.wasm +0 -0
  289. package/wasm/tree-sitter-php.wasm +0 -0
  290. package/wasm/tree-sitter-php_only.wasm +0 -0
  291. package/wasm/tree-sitter-powershell.wasm +0 -0
  292. package/wasm/tree-sitter-protobuf.wasm +0 -0
  293. package/wasm/tree-sitter-python.wasm +0 -0
  294. package/wasm/tree-sitter-ql.wasm +0 -0
  295. package/wasm/tree-sitter-r.wasm +0 -0
  296. package/wasm/tree-sitter-rescript.wasm +0 -0
  297. package/wasm/tree-sitter-ruby.wasm +0 -0
  298. package/wasm/tree-sitter-rust.wasm +0 -0
  299. package/wasm/tree-sitter-scala.wasm +0 -0
  300. package/wasm/tree-sitter-scss.wasm +0 -0
  301. package/wasm/tree-sitter-solidity.wasm +0 -0
  302. package/wasm/tree-sitter-sql.wasm +0 -0
  303. package/wasm/tree-sitter-svelte.wasm +0 -0
  304. package/wasm/tree-sitter-swift.wasm +0 -0
  305. package/wasm/tree-sitter-systemrdl.wasm +0 -0
  306. package/wasm/tree-sitter-terraform.wasm +0 -0
  307. package/wasm/tree-sitter-tlaplus.wasm +0 -0
  308. package/wasm/tree-sitter-toml.wasm +0 -0
  309. package/wasm/tree-sitter-tsx.wasm +0 -0
  310. package/wasm/tree-sitter-typescript.wasm +0 -0
  311. package/wasm/tree-sitter-vba.wasm +0 -0
  312. package/wasm/tree-sitter-vbnet.wasm +0 -0
  313. package/wasm/tree-sitter-vue.wasm +0 -0
  314. package/wasm/tree-sitter-xml.wasm +0 -0
  315. package/wasm/tree-sitter-yaml.wasm +0 -0
  316. package/wasm/tree-sitter-zig.wasm +0 -0
  317. package/wasm/tree-sitter.wasm +0 -0
  318. package/wasm/tsx.wasm +0 -0
  319. package/wasm/typescript.wasm +0 -0
@@ -0,0 +1,626 @@
1
+ import {
2
+ IDENTITY_TRAVERSABLE_TYPES,
3
+ memberFileMatches,
4
+ resolveMemberIdentity
5
+ } from "./chunk-VL52PL2T.js";
6
+ import {
7
+ classifyTier
8
+ } from "./chunk-RCMTFSEF.js";
9
+ import {
10
+ STORED_LINE_SELECT,
11
+ deriveCallSiteAddress,
12
+ graphTraversal,
13
+ storedLineFromRow,
14
+ unattributedAddress
15
+ } from "./chunk-47HKX4AJ.js";
16
+ import {
17
+ analysis_exclusions_exports
18
+ } from "./chunk-SM7RFQRO.js";
19
+
20
+ // src/lsp/impact.ts
21
+ var IMPACT_BUDGET = {
22
+ /** maximum reverse-dependency nodes in one impact set */
23
+ maxNodes: 200,
24
+ /** traversal depth cap (mirrors the engine's own hard cap of 10) */
25
+ maxDepth: 5
26
+ };
27
+ var IMPACT_TRAVERSABLE_ROOT_TYPES = IDENTITY_TRAVERSABLE_TYPES;
28
+ var IMPACT_REL_TYPES = ["calls", "instantiates", "extends", "implements"];
29
+ var IMPACT_TOTAL_SCAN_CEILING = 5e4;
30
+ var IMPACT_TOTAL_EXPANSION_CEILING = 2e3;
31
+ var IMPACT_TOTAL_TIME_CEILING_MS = 4e3;
32
+ var IMPACT_WALK_TIME_CEILING_MS = 1500;
33
+ var IMPACT_CALL_DEADLINE_MS = 3e3;
34
+ var IMPACT_COUNT_MIN_BUDGET_MS = 400;
35
+ function cascadePrefix(symbolId) {
36
+ return symbolId.includes(":") ? symbolId.slice(0, symbolId.lastIndexOf(":") + 1) : "";
37
+ }
38
+ function countImpactDependents(db, projectId, rootSymbolId, options = {}) {
39
+ const maxDepth = options.maxDepth ?? IMPACT_BUDGET.maxDepth;
40
+ const scanCeiling = options.scanCeiling ?? IMPACT_TOTAL_SCAN_CEILING;
41
+ const expansionCeiling = options.expansionCeiling ?? IMPACT_TOTAL_EXPANSION_CEILING;
42
+ const timeCeilingMs = options.timeCeilingMs ?? IMPACT_TOTAL_TIME_CEILING_MS;
43
+ const startedAt = Date.now();
44
+ const relTypes = options.relationshipTypes ?? IMPACT_REL_TYPES;
45
+ if (relTypes.length === 0) {
46
+ return {
47
+ total: null,
48
+ direct: 0,
49
+ rowsScanned: 0,
50
+ expansions: 0,
51
+ hitScanCeiling: false,
52
+ timedOut: false,
53
+ failed: true,
54
+ skipped: false,
55
+ depths: /* @__PURE__ */ new Map(),
56
+ parents: /* @__PURE__ */ new Map(),
57
+ cascade: null
58
+ };
59
+ }
60
+ const relPlaceholders = relTypes.map(() => "?").join(",");
61
+ const BATCH = 200;
62
+ const flatLevelSql = (n) => "SELECT s.id AS id, s.name AS name, f.relative_path AS file, r.target_symbol_id AS targetId FROM brain_relationship r JOIN brain_symbol s ON s.id = r.source_symbol_id AND s.project_id = r.project_id JOIN brain_file f ON f.id = s.file_id WHERE r.project_id = ? AND r.relationship_type IN (" + relPlaceholders + ") AND r.target_symbol_id IN (" + new Array(n).fill("?").join(",") + ")";
63
+ const rangeMembersSql = "SELECT id FROM brain_symbol WHERE project_id = ? AND owner_symbol_id >= ? AND owner_symbol_id < ?";
64
+ const TARGET_CHUNK = 400;
65
+ try {
66
+ const found = /* @__PURE__ */ new Set();
67
+ const depths = /* @__PURE__ */ new Map();
68
+ const opened = /* @__PURE__ */ new Set();
69
+ const visited = /* @__PURE__ */ new Set();
70
+ const parents = /* @__PURE__ */ new Map();
71
+ let frontier = [{ id: rootSymbolId, depth: 0 }];
72
+ let direct = 0;
73
+ let rowsScanned = 0;
74
+ let expansions = 0;
75
+ let hitScanCeiling = false;
76
+ let timedOut = false;
77
+ while (frontier.length > 0 && !hitScanCeiling) {
78
+ const next = [];
79
+ for (let start = 0; start < frontier.length; start += BATCH) {
80
+ if (Date.now() - startedAt > timeCeilingMs) {
81
+ timedOut = true;
82
+ hitScanCeiling = true;
83
+ break;
84
+ }
85
+ const batch = frontier.slice(start, start + BATCH).filter((entry) => !visited.has(entry.id));
86
+ if (batch.length === 0) continue;
87
+ for (const entry of batch) {
88
+ if (expansions >= expansionCeiling) {
89
+ hitScanCeiling = true;
90
+ break;
91
+ }
92
+ visited.add(entry.id);
93
+ opened.add(entry.id);
94
+ expansions++;
95
+ }
96
+ if (hitScanCeiling) break;
97
+ const targetIds = /* @__PURE__ */ new Set();
98
+ for (const entry of batch) targetIds.add(entry.id);
99
+ const ranges = /* @__PURE__ */ new Map();
100
+ for (const entry of batch) {
101
+ const prefix = cascadePrefix(entry.id);
102
+ if (!ranges.has(prefix)) ranges.set(prefix, prefix);
103
+ }
104
+ for (const prefix of ranges.values()) {
105
+ if (Date.now() - startedAt > timeCeilingMs) {
106
+ timedOut = true;
107
+ hitScanCeiling = true;
108
+ break;
109
+ }
110
+ const members = db.prepare(rangeMembersSql).all(projectId, prefix, prefix + ";");
111
+ for (const member of members) targetIds.add(member.id);
112
+ }
113
+ if (hitScanCeiling) break;
114
+ const flatTargets = [...targetIds];
115
+ const levelDepth = frontier[0].depth;
116
+ for (let chunkStart = 0; chunkStart < flatTargets.length; chunkStart += TARGET_CHUNK) {
117
+ if (Date.now() - startedAt > timeCeilingMs) {
118
+ timedOut = true;
119
+ hitScanCeiling = true;
120
+ break;
121
+ }
122
+ const chunk = flatTargets.slice(chunkStart, chunkStart + TARGET_CHUNK);
123
+ if (chunk.length === 0) continue;
124
+ const rows = db.prepare(flatLevelSql(chunk.length)).all(projectId, ...relTypes, ...chunk);
125
+ rowsScanned += rows.length;
126
+ for (const row of rows) {
127
+ if (!row.name || analysis_exclusions_exports.isNoiseSymbolName(row.name)) continue;
128
+ if (row.file && analysis_exclusions_exports.isHardNoisePath(row.file)) continue;
129
+ if (row.id === rootSymbolId) continue;
130
+ if (found.has(row.id)) continue;
131
+ found.add(row.id);
132
+ depths.set(row.id, levelDepth);
133
+ parents.set(row.id, row.targetId);
134
+ if (levelDepth === 0) direct++;
135
+ if (levelDepth + 1 < maxDepth) next.push({ id: row.id, depth: levelDepth + 1 });
136
+ }
137
+ if (rowsScanned > scanCeiling) {
138
+ hitScanCeiling = true;
139
+ break;
140
+ }
141
+ }
142
+ if (hitScanCeiling) break;
143
+ }
144
+ frontier = next;
145
+ }
146
+ return {
147
+ total: hitScanCeiling ? null : found.size,
148
+ direct,
149
+ rowsScanned,
150
+ expansions,
151
+ hitScanCeiling,
152
+ timedOut,
153
+ failed: false,
154
+ skipped: false,
155
+ depths,
156
+ parents,
157
+ cascade: classifyCascade(db, projectId, [...found], opened)
158
+ };
159
+ } catch {
160
+ return {
161
+ total: null,
162
+ direct: 0,
163
+ rowsScanned: 0,
164
+ expansions: 0,
165
+ hitScanCeiling: false,
166
+ timedOut: false,
167
+ failed: true,
168
+ skipped: false,
169
+ depths: /* @__PURE__ */ new Map(),
170
+ parents: /* @__PURE__ */ new Map(),
171
+ cascade: null
172
+ };
173
+ }
174
+ }
175
+ function classifyCascade(db, projectId, targets, opened) {
176
+ if (targets.length === 0) return { exact: 0, member: 0, rangeFolded: 0 };
177
+ const ownerById = /* @__PURE__ */ new Map();
178
+ try {
179
+ for (let i = 0; i < targets.length; i += 400) {
180
+ const slice = targets.slice(i, i + 400);
181
+ for (const row of db.prepare("SELECT id AS id, owner_symbol_id AS owner FROM brain_symbol WHERE project_id = ? AND id IN (" + slice.map(() => "?").join(",") + ")").all(projectId, ...slice)) {
182
+ ownerById.set(row.id, row.owner ?? null);
183
+ }
184
+ }
185
+ } catch {
186
+ return null;
187
+ }
188
+ let exact = 0;
189
+ let member = 0;
190
+ let rangeFolded = 0;
191
+ for (const id of targets) {
192
+ if (opened.has(id)) {
193
+ exact++;
194
+ continue;
195
+ }
196
+ const owner = ownerById.get(id) ?? null;
197
+ if (owner !== null && opened.has(owner)) member++;
198
+ else rangeFolded++;
199
+ }
200
+ return { exact, member, rangeFolded };
201
+ }
202
+ async function computeImpact(db, projectId, symbolName, deps = {}, memberFile, options = {}) {
203
+ const traverse = deps.traverse ?? graphTraversal;
204
+ const relationshipTypes = options.relationshipTypes ?? IMPACT_REL_TYPES;
205
+ if (relationshipTypes.length === 0) {
206
+ throw new Error(
207
+ "bb/impact: relationshipTypes must name at least one relationship kind \u2014 an empty list matches nothing and would be silently re-read as the engine's default"
208
+ );
209
+ }
210
+ const callStartedAt = Date.now();
211
+ const walk = {
212
+ direction: "callers",
213
+ maxDepth: IMPACT_BUDGET.maxDepth,
214
+ relationshipTypes: [...relationshipTypes],
215
+ timeBudgetMs: IMPACT_WALK_TIME_CEILING_MS
216
+ };
217
+ let resolution = { through: "direct" };
218
+ let result;
219
+ try {
220
+ result = await traverse(projectId, symbolName, walk);
221
+ } catch (error) {
222
+ const owner = resolveMemberIdentity(db, projectId, symbolName, memberFile);
223
+ if (owner === null) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ if (message.includes("not found")) {
226
+ throw new Error(`bb/impact: symbol '${symbolName}' not found \u2014 no impact set exists (declared error; run \`bb index\` or check the name)`);
227
+ }
228
+ throw error;
229
+ }
230
+ result = await traverse(projectId, owner.name, walk);
231
+ if (!result.root || !result.root.id) {
232
+ throw error;
233
+ }
234
+ resolution = { through: "owning-declaration", memberName: symbolName, ownerName: owner.name, ...memberFile ? { memberFile } : {} };
235
+ }
236
+ const rootType = result.root?.symbolType ?? "";
237
+ const rootedAtRequestedName = result.root?.name === symbolName;
238
+ const kindOutsideWhitelist = rootType.length > 0 && !IMPACT_TRAVERSABLE_ROOT_TYPES.includes(rootType);
239
+ const { candidates: nameCandidates, total: nameTotal } = listNameCandidates(db, projectId, symbolName);
240
+ const hintMatchesRoot = memberFile !== void 0 && typeof result.root?.file === "string" && memberFileMatches(memberFile, result.root.file);
241
+ const nameIsUnambiguous = nameTotal === 1 || (nameTotal === null || nameTotal > 1) && hintMatchesRoot;
242
+ const VALUE_LIKE_KINDS = ["enum_member", "constant"];
243
+ const exemptKind = kindOutsideWhitelist && rootedAtRequestedName && nameIsUnambiguous && VALUE_LIKE_KINDS.includes(rootType) ? rootType : null;
244
+ if (kindOutsideWhitelist && exemptKind === null) {
245
+ const owner = resolveMemberIdentity(db, projectId, symbolName, memberFile);
246
+ if (owner === null) {
247
+ const candidates = (nameTotal ?? 0) > 1 ? ` \u2014 ${nameTotal} stored symbols carry that name: ${nameCandidates.slice(0, 8).map((c) => `${c.file || "?"}:${c.line}`).slice(0, 8).join(", ")}${nameTotal !== null && nameTotal > 8 ? `, +${nameTotal - 8} more` : ""}; re-ask with the \`file\` option naming one of them` : "";
248
+ throw new Error(
249
+ `bb/impact: symbol '${symbolName}' is not a traversable root (${rootType}) \u2014 no impact set exists (declared error: the symbol was not found as a traversable root${memberFile ? "; the supplied file hint does not identify it \u2014 pass a project-relative path to its defining file" : "; run `bb index` or check the name"}${candidates})`
250
+ );
251
+ }
252
+ result = await traverse(projectId, owner.name, walk);
253
+ if (!result.root || !result.root.id) throw new Error(`bb/impact: symbol '${symbolName}' not found \u2014 no impact set exists (declared error)`);
254
+ resolution = { through: "owning-declaration", memberName: symbolName, ownerName: owner.name, ...memberFile ? { memberFile } : {} };
255
+ } else if (memberFile && result.root && rootType && IMPACT_TRAVERSABLE_ROOT_TYPES.includes(rootType)) {
256
+ if (typeof result.root.file !== "string" || !memberFileMatches(memberFile, result.root.file)) {
257
+ throw new Error(
258
+ `bb/impact: symbol '${symbolName}' resolves to '${result.root.file}' but the supplied file hint '${memberFile}' does not identify it \u2014 refusing to answer about a different copy (declared error; pass the symbol's own file, or omit \`file\` when the name is unique)`
259
+ );
260
+ }
261
+ }
262
+ if (!result.root || !result.root.id) {
263
+ throw new Error(`bb/impact: symbol '${symbolName}' not found \u2014 no impact set exists (declared error; run \`bb index\` or check the name)`);
264
+ }
265
+ const seen = /* @__PURE__ */ new Set();
266
+ const impact = [];
267
+ let truncated = false;
268
+ let truncationReason = null;
269
+ const edges = [...result.callers].sort((a, b) => a.depth - b.depth);
270
+ for (const edge of edges) {
271
+ if (impact.length >= IMPACT_BUDGET.maxNodes) {
272
+ truncated = true;
273
+ truncationReason = "impact-node-budget-exhausted";
274
+ break;
275
+ }
276
+ const nodeId = edge.from;
277
+ if (nodeId === result.root.id) continue;
278
+ if (seen.has(nodeId)) continue;
279
+ seen.add(nodeId);
280
+ const node = result.nodes[nodeId];
281
+ impact.push({
282
+ id: nodeId,
283
+ name: node?.name ?? nodeId,
284
+ file: node?.file ?? "",
285
+ filePath: node?.filePath ?? "",
286
+ // The ENGINE's address for this edge, served whole. The engine
287
+ // derived it from the same stored row this entry quotes (provisional
288
+ // evidence at the engine's first-trace depth; the re-read below replaces
289
+ // it whenever the depth moves). A dependent's DECLARATION is not a
290
+ // position claim about the usage, so it goes in its own declared field.
291
+ callSite: edge.callSite,
292
+ rowId: edge.rowId,
293
+ declarationLine: node?.line ?? 0,
294
+ symbolType: node?.symbolType ?? "",
295
+ language: node?.language ?? "",
296
+ depth: edge.depth,
297
+ // Provisional, and rewritten below wherever `depth` moved: the evidence
298
+ // below belongs to the engine's row, and the engine's row may not be the
299
+ // one at the shortest path this entry reports.
300
+ evidenceAtDepth: edge.depth,
301
+ relationshipType: edge.relationshipType,
302
+ confidence: edge.confidence,
303
+ // The ONE server-side classifier. A second derivation
304
+ // here would be the exact defect that module was written to close.
305
+ tier: classifyTier({
306
+ relationshipType: edge.relationshipType,
307
+ confidence: edge.confidence,
308
+ evidenceClass: edge.metadata?.evidenceClass ?? null
309
+ }),
310
+ isCrossLanguage: edge.isCrossLanguage === true,
311
+ bridgeMechanism: edge.bridgeMechanism ?? null,
312
+ // The engine already classifies every caller row as `exact` or
313
+ // `owner-cascade` and names the member it matched (bb_graph publishes
314
+ // both). Reading them here is the point: a second derivation would be the
315
+ // defect that module exists to prevent, and a dropped field is the defect
316
+ // was filed for.
317
+ via: edge.targetMatch === "owner-cascade" ? "member" : "exact",
318
+ ...edge.matchedMember ? { viaMember: edge.matchedMember } : {},
319
+ // The walk node this row was found at, which is what the cascade
320
+ // classification below judges the named member against. It stays off the
321
+ // wire: it is an internal id the reader has no use for, and `via` is the
322
+ // reader-facing half of the same fact.
323
+ tracedId: edge.to
324
+ });
325
+ }
326
+ const memberOwner = /* @__PURE__ */ new Map();
327
+ let unresolvedMembers = 0;
328
+ for (const entry of impact) {
329
+ if (entry.via !== "member" || !entry.viaMember) continue;
330
+ const traced = entry.tracedId ?? "";
331
+ const key = `${entry.viaMember.name}\0${entry.viaMember.file}\0${traced}`;
332
+ if (!memberOwner.has(key)) {
333
+ const owner = memberOwnerOf(db, projectId, entry.viaMember, traced);
334
+ memberOwner.set(key, owner);
335
+ if (owner === "unknown") unresolvedMembers++;
336
+ }
337
+ if (memberOwner.get(key) === "range-folded") entry.via = "range-folded";
338
+ }
339
+ const reasons = [];
340
+ for (const reason of (result.summary?.truncationReason ?? "").split("+")) {
341
+ const clause = reason.trim();
342
+ if (clause.length === 0 || clause.startsWith("relationshipTypes:")) continue;
343
+ truncated = true;
344
+ if (!reasons.includes(clause)) reasons.push(clause);
345
+ }
346
+ const walkTimeBoundFired = reasons.some((clause) => clause === "walk-time-budget-exhausted");
347
+ truncationReason = truncationReason ?? (reasons.length > 0 ? `traversal: ${reasons.join("+")}` : null);
348
+ if (walkTimeBoundFired) {
349
+ truncationReason = truncationReason === null ? "walk: time-budget-exhausted" : `${truncationReason} (walk: time-budget-exhausted \u2014 this answer is not reproducible)`;
350
+ }
351
+ const directFromSet = impact.reduce((n, entry) => entry.depth === 0 ? n + 1 : n, 0);
352
+ const depthReached = impact.reduce((n, entry) => entry.depth > n ? entry.depth : n, 0);
353
+ const boundaryReached = depthReached >= IMPACT_BUDGET.maxDepth - 1;
354
+ const boundarySuspect = boundaryReached && boundaryHasDependents(db, projectId, impact, relationshipTypes);
355
+ const verify = truncated || boundarySuspect;
356
+ const callElapsedMs = Date.now() - callStartedAt;
357
+ const countBudgetMs = Math.max(0, Math.min(IMPACT_TOTAL_TIME_CEILING_MS, IMPACT_CALL_DEADLINE_MS - callElapsedMs));
358
+ const countSkipped = verify && countBudgetMs < IMPACT_COUNT_MIN_BUDGET_MS;
359
+ const counts = !verify ? {
360
+ total: impact.length,
361
+ direct: directFromSet,
362
+ hitScanCeiling: false,
363
+ timedOut: false,
364
+ failed: false,
365
+ skipped: false,
366
+ depths: /* @__PURE__ */ new Map(),
367
+ parents: /* @__PURE__ */ new Map(),
368
+ cascade: null
369
+ } : countSkipped ? {
370
+ total: null,
371
+ direct: 0,
372
+ rowsScanned: 0,
373
+ expansions: 0,
374
+ hitScanCeiling: false,
375
+ timedOut: false,
376
+ failed: false,
377
+ skipped: true,
378
+ depths: /* @__PURE__ */ new Map(),
379
+ parents: /* @__PURE__ */ new Map(),
380
+ cascade: null
381
+ } : countImpactDependents(db, projectId, result.root.id, { maxDepth: IMPACT_BUDGET.maxDepth, relationshipTypes, timeCeilingMs: countBudgetMs });
382
+ if (counts.depths.size > 0) {
383
+ for (const entry of impact) {
384
+ const measured = counts.depths.get(entry.id);
385
+ if (measured !== void 0 && measured !== entry.depth) entry.depth = measured;
386
+ }
387
+ impact.sort((a, b) => a.depth - b.depth || a.id.localeCompare(b.id));
388
+ const reread = /* @__PURE__ */ new Map();
389
+ for (const entry of impact) {
390
+ if (entry.evidenceAtDepth === entry.depth) continue;
391
+ const parent = counts.parents.get(entry.id);
392
+ if (parent === void 0) {
393
+ entry.relationshipType = null;
394
+ entry.tier = null;
395
+ entry.isCrossLanguage = null;
396
+ entry.bridgeMechanism = null;
397
+ entry.confidence = null;
398
+ entry.evidenceAtDepth = null;
399
+ entry.callSite = unattributedAddress(entry.file);
400
+ continue;
401
+ }
402
+ const key = `${entry.id} ${parent}`;
403
+ if (!reread.has(key)) reread.set(key, storedRowEvidence(db, projectId, entry.id, parent, relationshipTypes));
404
+ const row = reread.get(key);
405
+ if (!row) {
406
+ entry.relationshipType = null;
407
+ entry.tier = null;
408
+ entry.isCrossLanguage = null;
409
+ entry.bridgeMechanism = null;
410
+ entry.confidence = null;
411
+ entry.evidenceAtDepth = null;
412
+ entry.callSite = unattributedAddress(entry.file);
413
+ continue;
414
+ }
415
+ entry.relationshipType = row.relationshipType;
416
+ entry.confidence = row.confidence;
417
+ entry.isCrossLanguage = row.isCrossLanguage;
418
+ entry.bridgeMechanism = row.bridgeMechanism;
419
+ entry.callSite = row.callSite;
420
+ entry.tier = classifyTier({
421
+ relationshipType: row.relationshipType,
422
+ confidence: row.confidence,
423
+ evidenceClass: row.evidenceClass
424
+ });
425
+ entry.evidenceAtDepth = entry.depth;
426
+ }
427
+ }
428
+ const unmeasurable = counts.hitScanCeiling || counts.failed || counts.skipped;
429
+ const total = unmeasurable ? null : counts.total;
430
+ const direct = unmeasurable ? null : counts.direct;
431
+ const depthCapFired = reasons.includes("caller-depth-cap-reached");
432
+ const provenComplete = verify && !unmeasurable && counts.timedOut !== true && counts.total === impact.length && !depthCapFired;
433
+ if (verify && counts.failed) {
434
+ truncated = true;
435
+ const clause = "count: measurement unavailable \u2014 the completeness walk could not read the index";
436
+ truncationReason = truncationReason === null ? clause : `${truncationReason} (${clause})`;
437
+ } else if (verify && counts.hitScanCeiling) {
438
+ truncated = true;
439
+ truncationReason = truncationReason ?? "impact-completeness-unverifiable-at-depth-boundary";
440
+ } else if (verify && counts.skipped) {
441
+ truncated = true;
442
+ const clause = `count: skipped \u2014 the walk left less than ${IMPACT_COUNT_MIN_BUDGET_MS} ms of the ${IMPACT_CALL_DEADLINE_MS} ms call deadline; the total was not measured`;
443
+ truncationReason = truncationReason === null ? clause : `${truncationReason} (${clause})`;
444
+ } else if (verify && counts.total !== impact.length) {
445
+ truncated = true;
446
+ truncationReason = truncationReason ?? "impact-set-shorter-than-unbounded-walk";
447
+ }
448
+ if (provenComplete && !walkTimeBoundFired) {
449
+ truncated = false;
450
+ truncationReason = null;
451
+ }
452
+ if (verify && counts.timedOut === true) {
453
+ truncated = true;
454
+ const clause = "count: time-budget-exhausted \u2014 this answer is not reproducible";
455
+ truncationReason = truncationReason === null ? clause : `${truncationReason} (${clause})`;
456
+ }
457
+ if (countSkipped) {
458
+ const clause = "count: skipped for the call deadline \u2014 this answer is not reproducible";
459
+ truncationReason = truncationReason === null ? clause : `${truncationReason} (${clause})`;
460
+ }
461
+ const summary = result.summary;
462
+ let cascade = { exact: 0, member: 0, rangeFolded: 0 };
463
+ if (unresolvedMembers === 0) {
464
+ let exact = 0;
465
+ let member = 0;
466
+ let rangeFolded = 0;
467
+ for (const entry of impact) {
468
+ if (entry.via === "exact") exact++;
469
+ else if (entry.via === "member") member++;
470
+ else rangeFolded++;
471
+ }
472
+ cascade = { exact, member, rangeFolded };
473
+ } else {
474
+ cascade = null;
475
+ }
476
+ return {
477
+ root: {
478
+ id: result.root.id,
479
+ name: result.root.name,
480
+ file: result.root.file,
481
+ filePath: result.root.filePath ?? "",
482
+ line: result.root.line ?? 0,
483
+ symbolType: result.root.symbolType ?? "",
484
+ language: result.root.language ?? ""
485
+ },
486
+ impact,
487
+ shownCount: impact.length,
488
+ totalCountIfKnown: total,
489
+ directCount: direct,
490
+ budget: {
491
+ maxNodes: IMPACT_BUDGET.maxNodes,
492
+ maxDepth: IMPACT_BUDGET.maxDepth,
493
+ totalScanCeiling: IMPACT_TOTAL_SCAN_CEILING,
494
+ totalExpansionCeiling: IMPACT_TOTAL_EXPANSION_CEILING,
495
+ totalTimeCeilingMs: countSkipped ? 0 : countBudgetMs,
496
+ walkTimeCeilingMs: IMPACT_WALK_TIME_CEILING_MS,
497
+ callDeadlineMs: IMPACT_CALL_DEADLINE_MS
498
+ },
499
+ traversal: {
500
+ depthReached: impact.reduce((n, entry) => entry.depth > n ? entry.depth : n, 0),
501
+ engineTruncated: summary?.truncated === true,
502
+ engineTruncationReason: summary?.truncationReason ?? null,
503
+ edgeLimit: summary?.edgeLimit ?? null,
504
+ cappedQueries: summary?.cappedQueries ?? null,
505
+ callersIfUncapped: summary?.callersIfUncapped ?? null,
506
+ returnedCallers: summary?.totalCallers ?? null,
507
+ walkTimeBudgetMs: summary?.timeBudgetMs ?? null,
508
+ walkTimeExhausted: summary?.timeBudgetExhausted === true,
509
+ totalsAreLowerBounds: summary?.totalsAreLowerBounds === true,
510
+ dedupedAtToolLayer: summary?.dedupedAtToolLayer ?? null
511
+ },
512
+ truncated,
513
+ truncationReason,
514
+ resolution,
515
+ rootMatch: {
516
+ requested: symbolName,
517
+ via: resolution.through === "owning-declaration" ? "owning-declaration" : result.root.name === symbolName ? exemptKind === null ? "exact" : "exact-exempt-kind" : "fuzzy-containment",
518
+ // From the SAME rows the declared error lists, so the count a reader sees
519
+ // in the payload and the list they see in a refusal are one measurement.
520
+ nameMatches: nameTotal !== null ? nameTotal : countExactNameMatches(db, projectId, symbolName)
521
+ },
522
+ rule: {
523
+ relationshipTypes: [...relationshipTypes],
524
+ cascade: "owner-cascade: a relationship stored against a member of a class is attributed to the whole owner family. The RANGE that implements it widens to the member's whole container, so siblings are folded in too; `cascade.rangeFolded` counts how many rows that accounts for in this answer",
525
+ noisePolicy: "hard-noise symbol names and paths are excluded, as in every other analysis surface",
526
+ maxDepth: IMPACT_BUDGET.maxDepth
527
+ },
528
+ cascade,
529
+ countVerification: {
530
+ ran: verify && !counts.skipped,
531
+ skipped: verify && counts.skipped,
532
+ timedOut: verify && counts.timedOut === true,
533
+ boundarySuspect,
534
+ complete: verify && !counts.skipped ? provenComplete : null
535
+ },
536
+ reproducible: !walkTimeBoundFired && !(verify && counts.timedOut === true) && !countSkipped
537
+ };
538
+ }
539
+ function storedRowEvidence(db, projectId, dependentId, parentId, relTypes) {
540
+ try {
541
+ const row = db.prepare(
542
+ "SELECT r.relationship_type AS relType, r.confidence AS confidence, r.is_cross_language AS isCross, r.bridge_mechanism AS bridge, r.metadata AS meta, " + STORED_LINE_SELECT + ", COALESCE(f.relative_path, f.path, '') AS srcFile, s.line_start AS srcLine FROM brain_relationship r LEFT JOIN brain_symbol s ON s.id = r.source_symbol_id LEFT JOIN brain_file f ON f.id = s.file_id WHERE r.project_id = ? AND r.source_symbol_id = ? AND r.target_symbol_id = ? AND r.relationship_type IN (" + relTypes.map(() => "?").join(",") + ") LIMIT 1"
543
+ ).get(projectId, dependentId, parentId, ...relTypes);
544
+ if (!row) return null;
545
+ let evidenceClass = null;
546
+ if (row.meta) {
547
+ try {
548
+ const parsed = JSON.parse(row.meta);
549
+ if (typeof parsed.evidenceClass === "string") evidenceClass = parsed.evidenceClass;
550
+ } catch {
551
+ evidenceClass = null;
552
+ }
553
+ }
554
+ return {
555
+ relationshipType: row.relType,
556
+ confidence: typeof row.confidence === "number" ? row.confidence : 0,
557
+ isCrossLanguage: row.isCross === 1,
558
+ bridgeMechanism: row.bridge ?? null,
559
+ // The address OWNER decides what this row's stored line IS. This function
560
+ // never sees a raw number to publish: a legacy `0` and a NULL both leave
561
+ // here as declared states, and only a real positive line can be an exact
562
+ // call site.
563
+ callSite: deriveCallSiteAddress({
564
+ storedLine: storedLineFromRow(row),
565
+ file: row.srcFile,
566
+ callerDeclaration: { line: row.srcLine }
567
+ }),
568
+ evidenceClass
569
+ };
570
+ } catch {
571
+ return null;
572
+ }
573
+ }
574
+ function memberOwnerOf(db, projectId, member, tracedId) {
575
+ try {
576
+ const file = db.prepare("SELECT id AS id FROM brain_file WHERE project_id = ? AND (relative_path = ? OR path = ?) LIMIT 1").get(projectId, member.file, member.file);
577
+ if (!file) return "unknown";
578
+ const rows = db.prepare("SELECT id AS id, owner_symbol_id AS owner FROM brain_symbol WHERE project_id = ? AND file_id = ? AND name = ?").all(projectId, file.id, member.name);
579
+ if (rows.length === 0) return "unknown";
580
+ const prefix = cascadePrefix(tracedId);
581
+ let sawRangeOnly = false;
582
+ for (const row of rows) {
583
+ if (row.owner === null || tracedId.length === 0 || row.owner === tracedId) return "member";
584
+ if (prefix.length === 0 || row.owner < prefix || row.owner >= prefix + ";") return "member";
585
+ sawRangeOnly = true;
586
+ }
587
+ return sawRangeOnly ? "range-folded" : "member";
588
+ } catch {
589
+ return "unknown";
590
+ }
591
+ }
592
+ function boundaryHasDependents(db, projectId, impact, relationshipTypes) {
593
+ const lastDepth = Math.max(0, ...impact.map((entry) => entry.depth));
594
+ if (lastDepth === 0) return false;
595
+ const boundary = impact.filter((entry) => entry.depth === lastDepth).map((entry) => entry.id);
596
+ if (boundary.length === 0) return false;
597
+ const placeholders = relationshipTypes.map(() => "?").join(",");
598
+ const row = db.prepare(
599
+ "SELECT 1 AS any FROM brain_relationship r JOIN brain_symbol s ON s.id = r.source_symbol_id AND s.project_id = r.project_id WHERE r.project_id = ? AND r.relationship_type IN (" + placeholders + ") AND r.source_symbol_id IN (" + boundary.map(() => "?").join(",") + ") LIMIT 1"
600
+ ).get(projectId, ...relationshipTypes, ...boundary);
601
+ return row !== void 0;
602
+ }
603
+ function listNameCandidates(db, projectId, symbolName) {
604
+ try {
605
+ const total = db.prepare("SELECT COUNT(*) AS c FROM brain_symbol WHERE project_id = ? AND name = ?").get(projectId, symbolName);
606
+ const candidates = db.prepare(
607
+ "SELECT COALESCE(f.relative_path, f.path, '') AS file, s.line_start AS line FROM brain_symbol s LEFT JOIN brain_file f ON f.id = s.file_id WHERE s.project_id = ? AND s.name = ? ORDER BY COALESCE(f.relative_path, f.path, ''), s.line_start LIMIT 32"
608
+ ).all(projectId, symbolName).map((row) => ({ file: row.file, line: typeof row.line === "number" ? row.line : 0 }));
609
+ return { candidates, total: typeof total?.c === "number" ? total.c : null };
610
+ } catch {
611
+ return { candidates: [], total: null };
612
+ }
613
+ }
614
+ function countExactNameMatches(db, projectId, symbolName) {
615
+ try {
616
+ const row = db.prepare("SELECT COUNT(*) AS c FROM brain_symbol WHERE project_id = ? AND name = ?").get(projectId, symbolName);
617
+ return typeof row?.c === "number" && row.c > 0 ? row.c : 1;
618
+ } catch {
619
+ return 1;
620
+ }
621
+ }
622
+
623
+ export {
624
+ IMPACT_BUDGET,
625
+ computeImpact
626
+ };