@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,613 @@
1
+ // src/mcp/tools/bb_search/descriptor.ts
2
+ var descriptor = {
3
+ name: "bb_search",
4
+ description: "Unified indexed retrieval for symbol lists, content, exact symbol source, or files. This is retrieval, not natural-language explanation or code analysis. `symbols` (default) searches indexed symbol NAMES with exact, prefix and substring phases, optional case folding (default: case-insensitive over every Unicode case pair), exact-name restriction, regex name matching (the query is then a JavaScript RegExp source, validated and bounded), language/symbolKind filters (case-insensitive against the indexed vocabulary), snippets and paging; it answers as text lines plus a final machine-readable match-set line (`[match-set] searchMode=symbols totalMatches=\u2026 returned=\u2026 truncated=\u2026 staleCount=\u2026`) so a short page is never read as a complete one. `content` searches the indexed symbol corpus \u2014 name, qualified name, docstring and the stored bounded symbol body \u2014 case-insensitively, and answers as JSON declaring the match set (`total`, `returned`, `truncated`, `truncationReason`), HOW it matched (`matchMode` phrase when the query appeared verbatim, terms when nothing matched verbatim and the query's content words were matched instead, with `termMatch` naming which words carried the best hit), `bodyCoverage` ({ withBody, symbols, ratio }) so 'nothing matched' is distinguishable from 'nothing could match', and a freshness block. `symbol` returns the exact indexed symbol record(s) with byte bounds, language and per-match freshness, declaring `found` and `totalMatches` (the complete match set; this mode has no paging). `file` returns current source for one indexed project-relative file, bounded to 100,000 characters with `contentLength`/`truncated` and a file-scoped freshness block; it never auto-refreshes. `symbol`, `content` and `symbols` modes auto-refresh stale files they touch within a bounded budget and declare the decision in an `autoRefresh` field; `refresh:false` opts out. Stale rows carry `stale:true` and the freshness block's remediation names the manual reindex command (file scope names that exact file). `limit` is a page size and a work budget: 0 returns no rows, values above 2000 are refused rather than clamped, and `limit`/`offset` are accepted only by the modes that page (`symbols`, `content`). `language`/`symbolKind` values the index does not contain are errors naming the indexed vocabulary; options a mode cannot honour are refused naming that mode's accepted set; an invalid regex is a named error; a candidate cap, projection cap or time budget is reported as `truncated` with `truncationReason` instead of a complete-looking page.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ mode: { type: "string", enum: ["symbols", "content", "symbol", "file"], description: "retrieval mode (default symbols)" },
9
+ query: { type: "string", description: "symbol/content/regex query, trimmed before use; also accepted as the exact symbol name in symbol mode. Not accepted in file mode" },
10
+ symbolName: { type: "string", description: "exact symbol name for symbol mode; supplying it with a different query is an error" },
11
+ filePath: { type: "string", description: "file target for file mode, or symbol disambiguation in symbol mode. Not accepted in symbols/content modes" },
12
+ limit: { type: "number", description: "max results (default 15; 0 returns no rows; above 2000 is refused; whole number). symbols/content modes page; symbol mode returns the COMPLETE match set when omitted and pages when given (its `totalMatches` is always the complete set size)" },
13
+ language: { type: "string", description: "filter by language, case-insensitive (e.g. typescript, python, go); a language this project does not index is an error. symbols/content modes only" },
14
+ symbolKind: { type: "string", description: "filter by symbol kind, case-insensitive (function, class, variable, interface, enum, ...); a kind this project does not index is an error. symbols/content modes only" },
15
+ includeContent: { type: "boolean", description: "include a code snippet per result (default false). symbols mode only" },
16
+ exact: { type: "boolean", description: "only exact name matches (default false); obeys caseSensitive. symbols mode only" },
17
+ caseSensitive: { type: "boolean", description: "case-sensitive matching (default false); folds every Unicode case pair, not only ASCII. symbols mode only" },
18
+ regex: { type: "boolean", description: "treat query as a regex against symbol names (default false). symbols mode only" },
19
+ offset: { type: "number", description: "pagination offset (default 0). symbols/content modes, and symbol mode when `limit` is given" },
20
+ refresh: { type: "boolean", description: "false opts out of the inline auto-refresh of stale files (default: auto-refresh within budget before answering). Accepted by symbols/content/symbol; file mode never auto-refreshes and does not accept it" }
21
+ },
22
+ required: []
23
+ },
24
+ mutability: "read",
25
+ requiresIndex: true,
26
+ modes: ["symbols", "content", "symbol", "file"],
27
+ freshnessModes: ["symbol", "content", "file"],
28
+ aliases: [],
29
+ deprecated: false,
30
+ documentation: { category: "search", summary: "Unified lexical, content, exact-symbol, and file retrieval.", defaults: { mode: "symbols", limit: 15, includeContent: false, exact: false, caseSensitive: false, regex: false, offset: 0 }, examples: [{ mode: "symbols", query: "handleRequest", limit: 5 }, { mode: "content", query: "serialize cache entry" }, { mode: "symbol", symbolName: "handleRequest" }, { mode: "file", filePath: "src/app.ts" }] }
31
+ };
32
+
33
+ // src/mcp/tools/bb_ask/descriptor.ts
34
+ var descriptor2 = {
35
+ name: "bb_ask",
36
+ description: "Answer a question about the codebase from the index, with the route and its limits stated. Four routes: (1) an EXACT AGGREGATE over indexed rows (counts of symbols/functions/classes/interfaces/structs/enums/traits/types/constants/relationships/imports/files, optionally by language; the largest file; the most complex function) \u2014 exact about the indexed snapshot, with that snapshot's time reported, and never a similarity claim; (2) an EXACT SYMBOL NAME lookup when the query is a single symbol name \u2014 matched by name, not by similarity, with its neighbours listed and labelled as neighbours; (3) SEMANTIC RETRIEVAL over stored embeddings \u2014 only vectors from the same model and dimension as the query are compared, similarities are cosine scores, and the answer reports which model space it compared, how many symbols exist in that space versus across the whole library, whether the scan cap was reached, and whether the provider embedding timed out (then a capped local 50-dimension fallback runs); (4) a KEYWORD FALLBACK when embeddings are unavailable or nothing clears the similarity floor \u2014 lexical matches that carry NO similarity score, with the reason stated. Every answer reports its confidence kind: a mean of returned similarities (bounded 0.2\u20130.85), a declared floor when no score was computed, or NO confidence number at all when the ranking is declared non-discriminative (the answer then says the scores do not separate candidates). Every answer declares `returned`/`totalMatches`/`truncated` bounds so a prefix is never read as a complete match set. Cited sources carry `[STALE \u2014 file changed since index]` or `[MISSING \u2014 file no longer on disk]` markers, and nothing is auto-refreshed. `limit` is the page size: whole number 1..100, default 5, values outside refused rather than clamped. When embeddings are configured, the natural-language question itself is sent to the configured embedding provider to compute the query vector; with local word vectors everything stays offline.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ query: { type: "string", description: "natural-language question, or a symbol name for the exact-name route" },
41
+ limit: {
42
+ type: "number",
43
+ minimum: 1,
44
+ maximum: 100,
45
+ description: "max results (default 5; whole number 1..100; values outside are refused, not clamped). A larger page also means a larger work budget."
46
+ }
47
+ },
48
+ required: ["query"]
49
+ },
50
+ mutability: "read",
51
+ requiresIndex: true,
52
+ modes: ["answer"],
53
+ plannedModes: ["explain", "feature"],
54
+ aliases: [],
55
+ deprecated: false,
56
+ documentation: {
57
+ category: "search",
58
+ summary: "Exact aggregates, exact-name lookups, and scored semantic or lexical matches \u2014 each labelled with its route and bounds.",
59
+ defaults: { limit: 5 },
60
+ examples: [
61
+ { query: "how many functions are in this project" },
62
+ { query: "where is request handling implemented" },
63
+ { query: "handleRequest", limit: 1 }
64
+ ]
65
+ }
66
+ };
67
+
68
+ // src/mcp/tools/bb_health/descriptor.ts
69
+ var descriptor3 = {
70
+ name: "bb_health",
71
+ description: 'Unified index health: file/symbol/relationship/imports counts, embedding model + coverage, schema version, runtime, file freshness, what this call cost, and (when asked) the integrity battery, analysis-history totals and index completeness. There is no `mode` parameter: a call reports status, and `refresh`/`force` select the one behaviour that mutates the index. The default output names WHY each file was classified: `freshness why` separates fresh-verified-by-content-hash from fresh-verified-by-mtime+size-only (the file was not read), and names content-changed, deleted-from-disk, unreadable and unindexed separately, because those need different fixes and a single \'N stale\' number is not actionable. Every report carries a top-level `healthy` boolean on the tool result (and inside the JSON payload when history=true): false exactly when the report contains an error-severity finding; a broken index is a successful REPORT, not a tool error (isError stays false), while wrong input is refused with isError. Options the chosen call cannot honour are refused: `limit` needs history=true; `wait` needs refresh/force; `sampleSize` belongs to sampled freshness only. Use verbose=true for a per-language breakdown, PER-LANGUAGE SEARCHABILITY (the share of each language\'s symbols a semantic query can reach \u2014 a language at 0% is invisible to bb_ask), the embedded/unembedded symbol kinds and stale-file names; verbose=true is also the only call that runs the per-symbol embedding aggregates, so a default call is bounded by the file count, not the symbol count. integrity=true runs the index integrity check battery, which NAMES each non-ok check with its remediation; with the default sampled freshness the battery falls back to a COMPLETE disk pass over the indexed files (it content-hashes files whose metadata changed), so it is materially more expensive than a status call. freshness="complete" (not a `complete` option) checks every indexed file instead of a sample; includeAnalysisRuns=true adds persisted run/finding totals; history=true answers with a JSON object { health, history, healthy } instead of the text report and is the only caller of `limit` (minimum 1, default 20). checkUnindexed=true walks the project and reports files the indexer would index but the index does not contain \u2014 a stat per eligible file, opt-in because a default call must not silently acquire a walk. refresh=true reindexes before reporting (`wait` defaults true; wait=false detaches it and the NEXT call reports the outcome once, success or failure); force=true wipes and rebuilds first and is DESTRUCTIVE. Freshness defaults to a DETERMINISTIC sample spread across the whole index: `stale` is then a lower bound on the project, not a total, and the report says so \u2014 pass freshness="complete" when you need the exact count.',
72
+ inputSchema: { type: "object", properties: { verbose: { type: "boolean", description: "include the per-language breakdown, per-language searchability, embedded/unembedded symbol kinds, embedding config/chain and stale/missing file names (default false). This is the only value that runs the per-symbol embedding aggregates; a default call skips them so it stays bounded by the file count" }, history: { type: "boolean", description: "return persisted analysis history (default false). CHANGES THE OUTPUT SHAPE: this mode answers with a JSON object { health, history, healthy } instead of the text report, so a caller must not parse the text form when asking for it; the result also carries the same top-level `healthy` boolean as the text form" }, limit: { type: "number", description: "analysis-history page size (default 20); must be a whole number >= 1 and is only read when history=true \u2014 supplying it without history is REFUSED" }, refresh: { type: "boolean", description: "reindex the project before reporting" }, force: { type: "boolean", description: "force a full in-place reindex before reporting \u2014 DESTRUCTIVE: the index is wiped before it is rebuilt, so a failure leaves it empty" }, wait: { type: "boolean", description: "applies to refresh/force only (default true; supplying it without refresh/force is REFUSED). wait=false detaches the rebuild and returns the current snapshot; the NEXT call reports the detached outcome once \u2014 completion as well as failure" }, freshness: { type: "string", enum: ["sampled", "complete"], description: 'freshness mode (default sampled \u2014 a deterministic, unbiased sample spread over every indexed file, NOT the first N; freshness="complete" checks every indexed file and makes the stale count exact)' }, sampleSize: { type: "number", description: 'freshness sample count (default 200); must be a whole number >= 1 and applies to freshness="sampled" only \u2014 supplying it together with freshness="complete" is REFUSED' }, integrity: { type: "boolean", description: "run the index integrity check battery (14 checks, each named with its remediation when not ok). Cost: with the default sampled freshness the battery performs a COMPLETE disk pass over the indexed files (content-hashing those whose metadata changed), so this is the most expensive opt-in" }, includeAnalysisRuns: { type: "boolean", description: "add persisted analysis run/finding totals (full-table counts, not a page length)" }, paths: { type: "array", items: { type: "string" }, description: "report the state of these project-relative files INSTEAD of a project-wide freshness answer \u2014 for the question an agent actually has after an edit, at O(files named) rather than O(project). At most 50, no duplicates, no blank entries. The answer is a SEPARATE self-describing block, so every other number in the report stays project-wide and the two can never be read as one. Each path is FIVE-state, not a boolean: fresh (indexed, on disk, unchanged), stale (content changed), gone (indexed, no longer readable on disk \u2014 `reason` distinguishes file-gone from unreadable), NOT-INDEXED (on disk, the indexer has not run since it appeared \u2014 a freshness verdict here would be meaningless), or absent (no such path). Paths that resolve outside the project, or through a symlink out of it, are REFUSED and reported as unchecked rather than answered" }, checkUnindexed: { type: "boolean", description: "WALK the project and report files on disk that the indexer would index but the index does NOT contain \u2014 the direction freshness cannot see (freshness reports files IN the index and gone from disk). Costs a stat per eligible file, i.e. the discovery phase of an index run, so it is opt-in. Uses the INDEXER'S OWN discovery rules, so gitignored files, node_modules, unsupported extensions and >5MB files are correctly NOT counted, and the output names that population. Without this option, index completeness is not asserted at all" } } },
73
+ mutability: "read",
74
+ requiresIndex: false,
75
+ modes: ["status", "refresh"],
76
+ aliases: [],
77
+ deprecated: false,
78
+ documentation: { category: "lifecycle", summary: "Unified index, schema, freshness, embeddings, runtime, integrity, completeness and analysis-history report \u2014 status by default, one explicit refresh behaviour, and no `mode` parameter.", defaults: { verbose: false, history: false, limit: 20, refresh: false, force: false, wait: true, freshness: "sampled", sampleSize: 200, integrity: false, includeAnalysisRuns: false }, examples: [{ verbose: true }, { history: true, limit: 5 }, { refresh: true, wait: false }, { force: true }, { integrity: true }, { freshness: "complete" }, { includeAnalysisRuns: true }, { paths: ["src/mcp/server.ts"] }, { checkUnindexed: true }] }
79
+ };
80
+
81
+ // src/mcp/tools/bb_patterns/descriptor.ts
82
+ var descriptor4 = {
83
+ name: "bb_patterns",
84
+ description: "Detect orphaned code, framework patterns, and anti-patterns, ranked by confidence, with staleness markers when a finding's file changed after indexing. Use scope=orphaned for dead code; scope=all (default) for the full pattern scan with the optional class/confidence filters. Orphan confidence is derived from evidence (0.30-0.70); anti-pattern confidence is 0.60-0.90; framework findings carry a fixed 0.6. `limit` is one bounded page (maximum 500) and the answer names the full census. Architecture and code-quality analysis are planned, not yet executable.",
85
+ inputSchema: {
86
+ type: "object",
87
+ properties: {
88
+ scope: {
89
+ type: "string",
90
+ enum: ["orphaned", "all"],
91
+ default: "all",
92
+ description: "orphaned only, or all pattern classes (default all)"
93
+ },
94
+ limit: {
95
+ type: "number",
96
+ default: 20,
97
+ minimum: 0,
98
+ maximum: 500,
99
+ description: "maximum findings displayed (default 20, maximum 500); the answer's census line reports the full count"
100
+ },
101
+ includeOrphaned: {
102
+ type: "boolean",
103
+ default: true,
104
+ description: "include orphaned-code patterns in an all scan (default true)"
105
+ },
106
+ includeFramework: {
107
+ type: "boolean",
108
+ default: true,
109
+ description: "include framework patterns (default true)"
110
+ },
111
+ includeAntiPatterns: {
112
+ type: "boolean",
113
+ default: true,
114
+ description: "include anti-patterns (default true)"
115
+ },
116
+ confidenceThreshold: {
117
+ type: "number",
118
+ default: 0,
119
+ minimum: 0,
120
+ maximum: 1,
121
+ description: "drop findings below this confidence (default 0 = report everything, ranked). Orphan confidence is evidence-derived and spans 0.30-0.70; anti-pattern confidence spans 0.60-0.90; framework findings are a fixed 0.6"
122
+ },
123
+ history: {
124
+ type: "boolean",
125
+ default: false,
126
+ description: "return persisted pattern history (runs bounded by limit; the findings window and its total are declared in the answer)"
127
+ },
128
+ recordRun: {
129
+ type: "boolean",
130
+ default: false,
131
+ description: "persist this scan as an analysis run (persists the whole census, not the displayed page; works in both scopes; capped at 50,000 findings with the cap disclosed)"
132
+ }
133
+ }
134
+ },
135
+ mutability: "read",
136
+ requiresIndex: true,
137
+ modes: ["orphaned", "all"],
138
+ plannedModes: ["quality", "architecture"],
139
+ aliases: [],
140
+ deprecated: false,
141
+ documentation: {
142
+ category: "analysis",
143
+ summary: "Pattern/orphan analysis ranked by confidence, with a bounded page, a declared census, per-finding staleness, and optional persisted run history.",
144
+ defaults: { scope: "all", limit: 20, includeOrphaned: true, includeFramework: true, includeAntiPatterns: true, confidenceThreshold: 0, history: false, recordRun: false },
145
+ examples: [{ scope: "orphaned" }, { confidenceThreshold: 0.6 }, { recordRun: true }]
146
+ }
147
+ };
148
+
149
+ // src/mcp/tools/bb_security/descriptor.ts
150
+ var descriptor5 = {
151
+ name: "bb_security",
152
+ // What the code actually detects, and what it does not: no writer emits a
153
+ // hardcoded-secret vulnerability type (that rule belongs to bb_patterns), and
154
+ // weak crypto is emitted only as an isolated sink, so it stays an unvalidated
155
+ // observation rather than a finding. "Cross-language" is retained because
156
+ // cross-file taint resolution exists and is reported, with the flag's precise
157
+ // meaning stated in docs/BB_SECURITY.md.
158
+ description: "Taint-flow security scan over the indexed source-to-sink flows: SQL injection, XSS, command injection, path traversal, insecure deserialization, SSRF, open redirect and code execution. Flows whose endpoints both resolve to indexed symbols are findings; one-sided flows are reported separately as unvalidated observations, never as confirmed exploits. Reads are read-only against the index; an unindexed project is refused by name. Findings whose underlying file changed after indexing carry a staleness marker.",
159
+ inputSchema: { type: "object", properties: { history: { type: "boolean", description: "return persisted security history" }, recordRun: { type: "boolean", description: "persist this scan as an analysis run" }, limit: { type: "number", description: "history run limit (default 20)" } } },
160
+ mutability: "read",
161
+ requiresIndex: true,
162
+ modes: ["findings"],
163
+ plannedModes: ["flow", "summary", "hotspots"],
164
+ aliases: [],
165
+ deprecated: false,
166
+ documentation: {
167
+ category: "analysis",
168
+ summary: "Taint-flow findings over the index, with optional persisted run history. A flow is a finding only when both endpoints resolve to indexed symbols; one-sided flows are reported as unvalidated observations. A findings answer states the pre-cap census so a capped scan is never mistaken for a complete one; a history answer states its run count and its finding total, and its findings page is capped and declared. Reads open the index read-only and refuse an unindexed project; recordRun is the one write. Not detected: hardcoded secrets; genuine cross-language taint; standalone weak crypto.",
169
+ defaults: { history: false, recordRun: false, limit: 20 },
170
+ examples: [{}, { recordRun: true }, { history: true }]
171
+ }
172
+ };
173
+
174
+ // src/mcp/tools/bb_visualize/descriptor.ts
175
+ var descriptor6 = {
176
+ name: "bb_visualize",
177
+ description: 'Scoped dependency and architecture visualization: one bounded projection over the index\'s stored symbol and relationship rows, with explicit filters, provenance, truncation, machine-readable JSON, and genuinely self-contained HTML (no network, no fonts, no build step \u2014 it renders offline, in an air-gapped editor, anywhere). The answer is an AUDITABLE projection of the index, not a picture: every link carries the stored relationship row it traces to (rowId) and its stored confidence, and every node carries its line, lineEnd and qualifiedName, so the graph is navigable and every edge can be checked against the row that justifies it. Distinct relationship types between the same pair are SEPARATE links, so a pair that is both contains and exports renders as both. Node size follows the index\u2019s own gravity score. In the HTML, edge opacity encodes stored confidence, relationship type sets colour and a structural/behavioural dash, a CROSS-LANGUAGE edge gets its own wide dash and is drawn beneath the local edges so a polyglot project shows where one language hands off to another, and a stale endpoint (file changed since index) is drawn as a hollow ring; links inherit worst-of-endpoints. Nodes are grouped into the NAMED REGION they live in \u2014 a directory of the project, a directory with fewer than three matching symbols folded into its parent \u2014 each region placed at its own anchor inside a rounded pod and switchable from the sidebar like any other class, At overview zoom an edge BETWEEN two regions is drawn as one REGION LINK per region pair \u2014 dotted, because it is a summary and not a stored row; its width and alpha come from how many stored edges it stands for \u2014 while an edge INSIDE one region is always drawn as itself, and the individual cross-region edges return past ~1.15x zoom or when a link is opened (double-click frames both of its regions). Edges drawn individually are capped at a zoom-dependent count with the readout naming the cap, because an uncapped aggregate is a smear rather than a map; the frame\'s readout reconciles the picture with the census (how many cross-region edges the links stand for, how many pairs exist, how to open them), the sidebar\'s live line repeats it, and the links \u2014 with their regions, counts, stored rows, commonest relationship and screen geometry \u2014 are PUBLISHED on the view\'s API rather than only painted, because a summary that can only be seen cannot be checked. Moving the pointer across the map never changes the view: hover emphasises the node\'s own edges, and a CLICK is what focuses, so the map cannot dissolve under the reader\'s hand. The graph has TWO arrangements and the reader picks: `layout: "regions"` (the default) is the region map described above, and `layout: "force"` drops the directories entirely \u2014 one force layout, no region pods and no region links, every stored edge drawn as itself under the same zoom-dependent cap \u2014 which is the graph as it looked before the regions existed, and which is the honest shape for a project whose parts do not fall along directory lines. The layout is written onto the document itself (data-bb-layout) and both arrangements are offered as a switch in the generated page\'s sidebar, so `layout` sets the arrangement the page OPENS with and the reader can move between them without regenerating anything. The picked-item panel is deliberately not a modal: it docks to either side of the frame and collapses to its own heading \u2014 which always names what is picked \u2014 while KEEPING the selection, because hiding a panel to see the picture must not cost the reader the focus and the zoom they just asked for (its X is the control that clears). The layout runs in frame-sized slices with a progress readout, so the artifact stays interactive and visible at the maximum graph size and in a background tab. Every answer declares its bounds as three DISTINCT numbers \u2014 totalNodesAvailable (what the scope and filters actually match), truncation.originalNodes (what reached the projection) and nodes (what was returned) \u2014 and the HTML artifact carries the same declaration in its machine-readable payload (`declared`), so a short page can never be mistaken for a whole set in any format. `offset` walks that same order rather than re-reading the first page: the ranking carries a final `s.id` tiebreaker so page k+1 begins exactly where page k ended, and `considered.page` declares which page the answer is, of how many, and where the next one starts (null on the last) \u2014 a scope larger than one page can be walked, and an offset past the end is stated as an empty PAGE rather than as an empty project. Whatever the noise filter removes is counted onto excluded with the patterns responsible \u2014 always present, including when it is empty, so silence and zero are never confused \u2014 and an empty answer is declaredAbsent with a reason rather than an empty result that looks like a real project. `considered` declares what the answer actually READ: the relationship rows connecting the returned nodes (complete for that node set, not a slice of the relationship table), the selection order that chose the nodes, the applied filters, and the symbol-kind and language census of what was returned, plus the annotation overlay and the owner-cascade counts. `considered.overlays` carries the index\'s OTHER facts about the returned symbols \u2014 agent annotations from `bb_annotate`, matched by NAME AND PATH (never by name alone: two symbols can share a name, and a note is about one of them), with the notes themselves, the author class, and a count of notes that name a symbol of this scope at another path and are therefore NOT attached. The layer is declared even when it is empty, so "no agent has annotated these symbols" and "nobody looked" are different replies. Language filters are applied BEFORE the limit, so a rare language receives the same budget as a common one. The DEFAULT text format carries the same declared coverage, selection and exclusion as the JSON one, so the reply a caller gets without asking is the reply that can be audited. Member-level relationships are FOLDED onto the owner class, and every folded edge says so: when a class-like symbol is selected and a relationship is stored on one of its MEMBERS that is not itself in the node set, the row is attributed to the owner and the edge carries ownerCascade with matchedMembers naming the member it came through \u2014 so an attributed edge is never presented as one the index stored between the two drawn nodes. The fold is deliberately narrow (one level, class-like owners only, never for contains, an exact edge always preferred to an attributed one) and `considered.ownerCascade` declares whether it ran, how many members it scanned, how many rows it folded and whether a cap truncated it, so "nothing to fold" and "the fold never ran" are distinguishable. This is NOT bb_graph\'s depth-N traversal: the edge set is the relationships between the selected nodes, plus the folded member rows.',
178
+ inputSchema: { type: "object", properties: { scope: { type: "string", enum: ["project", "file", "module", "symbol", "feature"], description: "graph scope (default project)" }, target: { type: "string", description: "file, module, symbol, or feature target (`symbol` and `query` are accepted as aliases for `target`)" }, maxNodes: { type: "number", description: "max nodes (default 200, accepted range 1-2000); 0 and anything above the ceiling are REFUSED with the range named rather than clamped, because a returned page must mean the page that was asked for" }, maxLinks: { type: "number", description: "max links (default 5000, accepted range 1-20000); 0 and anything above the ceiling are REFUSED with the range named rather than clamped, because a returned page must mean the page that was asked for" }, offset: { type: "number", description: "how many symbols of the ranking to SKIP before this page starts (default 0; a whole number >= 0, refused rather than normalised). The order is the scope's own ranking with a final `s.id` tiebreaker, so page k+1 begins where page k ended and no symbol is repeated or lost. `considered.page` states the page number, the page count, the total matching the scope, and the next offset (null on the last page). Without this, a scope bigger than `maxNodes` had symbols that could not be asked for at all" }, relationshipTypes: { type: "array", items: { type: "string" }, description: "relationship type filter. Matched case-insensitively against the index's OWN stored spellings, and a type the index does not contain is REJECTED with the stored set named (never answered as zero links, which would read like a project with no calls). The applied filter is echoed in considered.relationshipTypesConsidered." }, symbolKinds: { type: "array", items: { type: "string" }, description: "symbol kinds to KEEP (class, interface, struct, enum, function, method, property, constant, variable, ...). Omitted = every kind, and on a real project gravity ranks files\u2019 constants and properties first: the reply states the composition it returned in `considered.kindCensus`, so a graph of string constants is visible as one rather than mistaken for the architecture. Matched case-insensitively against the index\u2019s own spellings, and a kind the index does not contain is REJECTED with the stored set named (never answered as zero nodes). The applied filter is echoed in considered.symbolKindsConsidered. The filter also scopes the symbols the owner cascade would add; removed additions are counted on considered.ownerCascade.nodesFiltered." }, languages: { type: "array", items: { type: "string" }, description: "language filter. Matched case-insensitively against the index's OWN stored spellings, and an unknown language is REJECTED with the stored set named (never answered as zero nodes). Applied BEFORE the node limit, so a rare language receives the same budget as a common one, and the applied filter is echoed in considered.languagesConsidered." }, excludePatterns: { type: "array", items: { type: "string" }, description: "path SEGMENTS treated as build noise and excluded. Matched on whole segments, never as substrings \u2014 a substring rule removed `src/util/edit_distance.rs` because `dist` is inside `edit_distance`, and `src/compiler/layout.rs` because `out` is inside `layout`. Omit to use the defaults (node_modules, .git, .next, .turbo, .cache, coverage, dist, build, out, target, vendor, __pycache__)" }, includeTests: { type: "boolean", description: "include test/spec sources. Default true: they are ordinary source. When false, test/spec/testdata directories and *_test.* / *spec.* files are excluded and counted" }, view: { type: "string", enum: ["graph"], default: "graph", description: "The dependency map (nodes are symbols, lines are relationships, sized by stored evidence). Accepts only `graph`; any other value is REFUSED rather than answered as the graph." }, layout: { type: "string", enum: ["regions", "force"], default: "regions", description: "the GRAPH view's arrangement, and the only option here that shapes the picture without changing the data. `regions` (default) groups every node into the DIRECTORY it lives in, anchors and names each region, and summarises the edges between regions as one dotted region link per pair at the overview. `force` ignores directories entirely: one force layout, no pods and no region links, every stored edge drawn as itself under a zoom-dependent cap the readout names. The generated page carries BOTH as a switch in its sidebar, so this sets the arrangement the page OPENS with; the layout is declared on the document itself (data-bb-layout), so the file draws what it says it is." }, format: { type: "string", enum: ["text", "json", "html"], description: "output format (default text)" } } },
179
+ mutability: "read",
180
+ requiresIndex: true,
181
+ modes: ["project", "file", "module", "symbol", "feature"],
182
+ aliases: [],
183
+ deprecated: false,
184
+ documentation: { category: "analysis", summary: "Scoped canonical dependency graphs with explicit truncation and provenance.", defaults: { scope: "project", maxNodes: 200, maxLinks: 5e3, offset: 0, format: "text" }, examples: [{ scope: "symbol", target: "handleRequest", format: "json" }, { scope: "module", target: "src/api", format: "html" }] }
185
+ };
186
+
187
+ // src/mcp/tools/bb_migrate/descriptor.ts
188
+ var descriptor7 = {
189
+ name: "bb_migrate",
190
+ description: "Read-only single or batch migration planning with dependency order, explicit effort methodology, library mappings, risks, truncation, and unsupported cases. A request is a list of entries (symbol names, indexed symbol ids, project-relative paths) unioned and capped at 100; every entry resolves to at most one component before any relationship is read, and a component named through more than one entry is planned once. Plans whose source file changed since the last index are flagged stale up front \u2014 a plan is a claim about current code; JSON output carries the freshness block.",
191
+ inputSchema: {
192
+ type: "object",
193
+ properties: {
194
+ symbolName: { type: "string", description: "legacy single-symbol alias for symbolNames; when both are supplied the entries are UNIONED, never silently dropped" },
195
+ symbolNames: { type: "array", items: { type: "string" }, description: "one or more component names; a name that matches several symbols in different files is refused with candidate ids to re-run against symbolIds. Blank entries are refused by name" },
196
+ paths: { type: "array", items: { type: "string" }, description: "project-relative file or DIRECTORY paths (absolute paths inside the project are accepted); every indexed symbol in them joins the request, so a whole module can be planned in one call. UNION with symbolNames, not a replacement: naming a symbol AND a path that contains it plans it once. A path that matches no indexed file is refused by name" },
197
+ symbolIds: { type: "array", items: { type: "string" }, description: "indexed symbol ids (shape `<projectId>:<relative file>:<name>:<line>`), the ONLY unambiguous way to name a component. Use this when a refusal reported that a name matches several symbols; a name is not an identity, and ids come from an earlier result's plans or an ambiguity refusal's candidates. An id no symbol carries is refused by name" },
198
+ targetLanguage: { type: "string", description: "e.g. rust, go, python. An unknown target is refused; a known target with no dictionary rows for the source pair is planned with the missing-data gap declared and no mapping invented. Blank is refused" },
199
+ format: { type: "string", enum: ["text", "json"], description: "output format (default text)" }
200
+ },
201
+ required: ["targetLanguage"]
202
+ },
203
+ mutability: "read",
204
+ requiresIndex: true,
205
+ modes: ["plan"],
206
+ freshnessModes: ["plan"],
207
+ aliases: [],
208
+ deprecated: false,
209
+ documentation: {
210
+ category: "analysis",
211
+ summary: "Batch cross-language migration plans with ordering, waves, cohort rollup, scope completeness, and unsupported mappings.",
212
+ defaults: { format: "text" },
213
+ examples: [
214
+ { symbolName: "handleRequest", targetLanguage: "go" },
215
+ { symbolNames: ["parse", "handle"], targetLanguage: "rust", format: "json" },
216
+ { paths: ["src/api"], targetLanguage: "go" },
217
+ { symbolIds: ["<projectId>:src/api/handler.ts:handleRequest:12"], targetLanguage: "go" }
218
+ ]
219
+ }
220
+ };
221
+
222
+ // src/mcp/tools/bb_analyze/descriptor.ts
223
+ var descriptor8 = {
224
+ name: "bb_analyze",
225
+ description: "Unified evidence-backed analysis over graph relationships, dependencies, project statistics, complexity, impact, and test intelligence. Select exactly one mode. Every read mode carries a freshness block (fresh/stale/missing counts + remediation) resolved against the live working tree, and report rows carry a stale flag when their file changed after indexing. Every mode is BOUNDED and says so: `limit` is the most items returned (edges per direction for callers/callees/trace, files for dependencies, entries for the test modes, the same everywhere) and the answer reports what it returned against what exists, so a page is never read as the whole set. callers/callees/trace/impact share the graph engine's owner-cascade semantics \u2014 a relationship stored against a class member is attributed to the owner class \u2014 and every edge states which it was through `targetMatch`. WHICH symbol the name resolved to is declared as well: `rootMatch` is `exact`, `exact-any-kind` or `containment`, and `containment` means the answer is about a symbol whose name CONTAINS the one asked for, or differs only in case \u2014 `found: true` on its own does not mean the name was itself a symbol. Locations are project-relative. Per-mode parameters: target applies to callers, callees, trace, complexity, impact, and test_impact; filePath scopes dependencies, test_mapping, test_gaps, and test_quality to a project-relative path PREFIX and is REFUSED (not ignored) on every other mode; limit applies to callers, callees, trace, dependencies, test_mapping, test_gaps and test_quality and is refused elsewhere; stats is always project-wide.",
226
+ inputSchema: { type: "object", properties: { mode: { type: "string", enum: ["callers", "callees", "trace", "dependencies", "stats", "complexity", "impact", "test_mapping", "test_gaps", "test_impact", "test_quality"], description: "analysis mode" }, target: { type: "string", description: "A symbol name for callers/callees/trace/impact/test_impact, or a project-relative FILE PATH for complexity (an exact file, or a directory prefix at any depth INCLUDING a top-level one; anything else is refused rather than treated as a path fragment). `symbol`/`symbolName`/`query` are accepted as aliases. A target is a VALUE, never a pattern: `%`, `_` and `\\` are escaped and match themselves. Echoed back bounded at 200 characters with its full length stated \u2014 the search always uses the whole string." }, filePath: { type: "string", description: "Project-relative path PREFIX scoping dependencies, test_mapping, test_gaps and test_quality. Refused on every other mode. Matched on a whole segment boundary, so `src/util` does not match `src/utilities`." }, limit: { type: "number", description: "Maximum items returned: edges per direction for callers/callees/trace, files for dependencies, entries for test_mapping/test_gaps/test_quality. Defaults: 25 edges per direction, 20 files, 50 entries. Range 1-5000 (graph modes) or 1-2000 (the rest); 0 and anything above the ceiling are REFUSED rather than clamped, because a returned page must mean the page that was asked for. The answer reports what it returned and whether that is the whole set. Refused on the modes it does not apply to." } }, required: ["mode"] },
227
+ mutability: "read",
228
+ requiresIndex: true,
229
+ modes: ["callers", "callees", "trace", "dependencies", "stats", "complexity", "impact", "test_mapping", "test_gaps", "test_impact", "test_quality"],
230
+ // Every bb_analyze mode is a read path over the index — all carry
231
+ // the staleness envelope on their output.
232
+ freshnessModes: ["callers", "callees", "trace", "dependencies", "stats", "complexity", "impact", "test_mapping", "test_gaps", "test_impact", "test_quality"],
233
+ aliases: [],
234
+ deprecated: false,
235
+ documentation: {
236
+ category: "analysis",
237
+ summary: "One typed analysis surface over the graph, the file graph, project counts, complexity, impact and test evidence. Every mode is bounded, declares its own bounds, and reports what it returned against what exists.",
238
+ // The REAL defaults, per mode. A single number was wrong in every mode:
239
+ // 25 edges per direction for the graph modes, 20 files for dependencies,
240
+ // 50 entries for test_mapping/test_gaps, 50 files for test_quality.
241
+ defaults: {
242
+ limit: "25 edges per direction (callers/callees/trace); 20 files (dependencies); 50 entries (test_mapping/test_gaps); 50 files (test_quality)",
243
+ target: "required for callers, callees, trace, impact, test_impact; optional file path or directory prefix for complexity",
244
+ filePath: "optional path prefix (or an exact file) for dependencies, test_mapping, test_gaps, test_quality"
245
+ },
246
+ examples: [{ mode: "callers", target: "handleRequest" }, { mode: "complexity", target: "src/api" }, { mode: "test_quality" }, { mode: "dependencies", filePath: "src/core" }, { mode: "impact", target: "handleRequest", limit: 50 }]
247
+ }
248
+ };
249
+
250
+ // src/mcp/tools/bb_doc/descriptor.ts
251
+ var descriptor9 = {
252
+ name: "bb_doc",
253
+ description: "Generate read-only, evidence-backed documentation with resolvable citations and a freshness block over the cited files. Every mode emits its declared section set, an empty result is never reported as passing, and the output budget is enforced on the payload the caller receives: `quality.budget.estimatedTokens` is measured on the decorated answer, and `estimatedTokens * 4` is never smaller than what was sent. When the required sections cannot fit, the result says so rather than dropping one. `quality.references` counts the citations the answer EMITS (plus the `collected*` totals of everything gathered), so the stale/missing numbers match the Sources list. Supports symbol, file, module, feature, API, project, and documentation-coverage modes in Markdown or JSON. When the cited files are stale, the handler auto-refreshes exactly those files within the shared budget and regenerates the document; refresh=false opts out and answers from existing data with the freshness block. A module target that matches no indexed file is refused by name rather than answered with project-wide facts, and `limit`/`maxTokens` outside their ranges are refused rather than clamped.",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {
257
+ mode: { type: "string", enum: ["symbol", "file", "module", "feature", "api", "project", "coverage"], description: "documentation mode" },
258
+ target: {
259
+ type: "string",
260
+ description: "symbol, file, module, feature, or API target (`symbol`/`symbolName`/`query` are accepted as aliases); optional for project and coverage. `symbol` matches an indexed name, qualified name or its last dotted/`::` segment. `file` accepts an exact repository-relative path, an absolute path, a directory prefix, or a bare file name with or without its extension. `module` needs a repository-relative directory the index holds; a target that matches nothing is REFUSED with the forms tried. `project` accepts a target but uses it as document metadata only \u2014 it does not scope the selection."
261
+ },
262
+ format: { type: "string", enum: ["markdown", "json"], description: "output format (default markdown)" },
263
+ audience: { type: "string", enum: ["developer", "maintainer", "user", "website"], description: "documentation audience: selects OPTIONAL sections only; a required section is never dropped for a smaller audience" },
264
+ detail: { type: "string", enum: ["brief", "standard", "deep"], description: "detail level: `brief` caps each section at 5 facts and drops optional sections, `standard` caps at `limit`, `deep` caps at 3x`limit` and adds the optional all-edges section; for `feature` it also enables semantic candidate expansion, which is much slower" },
265
+ includeExamples: { type: "boolean", description: "include source-backed examples. No example is emitted when no cited snippet or verified contract supplies one; an honest not-observed unknown is recorded instead" },
266
+ includeDiagrams: { type: "boolean", description: "include cited diagrams when available. This implementation does not emit diagrams; `reference.diagrams` stays empty and an honest not-observed unknown is recorded" },
267
+ includeTests: { type: "boolean", description: "include indexed test evidence. Accepted for `symbol` and `feature` modes only (their documents render a test section); the other modes REFUSE it because it would do nothing. Absent means the mode default (on); explicit false suppresses the test facts" },
268
+ limit: { type: "number", minimum: 1, maximum: 100, description: "maximum evidence items per section (whole number 1-100, default 20). Out-of-range values are REFUSED, not clamped, so the page returned is the page requested" },
269
+ maxTokens: { type: "number", minimum: 1, maximum: 16e3, description: "output token budget measured on the emitted payload (whole number 1-16000, default 8000; the default clears the measured minimum honest rendering on the reference index, ~6,700 tokens). `estimatedTokens` reports that measurement; when the required sections cannot fit, `budget.passed` is false and a shortfall unknown names the smallest honest rendering. Out-of-range values are REFUSED" },
270
+ refresh: { type: "boolean", description: "false opts out of the inline auto-refresh of the files this document cites (default: auto-refresh those files within the shared budget, then regenerate); the decision is always declared in `autoRefresh` or the answer text" }
271
+ },
272
+ required: ["mode"]
273
+ },
274
+ mutability: "read",
275
+ requiresIndex: true,
276
+ modes: ["symbol", "file", "module", "feature", "api", "project", "coverage"],
277
+ aliases: [],
278
+ deprecated: false,
279
+ documentation: {
280
+ category: "analysis",
281
+ summary: "Cited Markdown or JSON documentation over indexed source and relationships, with declared bounds, emitted-citation freshness counts, and a payload-measured output budget.",
282
+ defaults: { format: "markdown", audience: "developer", detail: "standard", includeExamples: false, includeDiagrams: false, includeTests: false, limit: 20, maxTokens: 8e3 },
283
+ examples: [
284
+ { mode: "symbol", target: "handleRequest" },
285
+ { mode: "feature", target: "request handling", format: "json", detail: "deep" },
286
+ { mode: "coverage", target: "src", audience: "maintainer" }
287
+ ]
288
+ }
289
+ };
290
+
291
+ // src/mcp/tools/bb_edit/descriptor.ts
292
+ var descriptor10 = {
293
+ name: "bb_edit",
294
+ description: "Unified, validated source mutation. Discriminated by mode: write, update, insert, delete, or refactor. Supports dry-run previews, separator/symlink-safe paths, AST byte bounds, caller protection, rollback on validation failure, formatting, and reindexing. If the target file changed since it was indexed, this tool refreshes that one file and re-resolves the symbol before editing (reported in warnings) \u2014 an edit is never applied to stale byte bounds, and a symbol that was renamed or removed is refused by name. The bytes each target had when the edit was PLANNED are re-checked immediately before the write, so a file that changed in between (an editor saving during a multi-file refactor, say) is refused with nothing written rather than overwritten from a plan computed against its previous contents. A project with no index is not a dead end: `write` plans from the request and the disk, resolves no symbol, and reindexes the file it wrote (so `reindexed` is how a brand-new project gets its first indexed rows), while `update`, `insert`, `delete` and `refactor` resolve their target through the index and are refused \u2014 with the mode that still works named beside the reindex command \u2014 until one exists. `write` replacing an existing file is guarded: a replacement that removes at least half of a file's lines (and at least 5 lines) is refused as a probable mistake unless `overwrite: true` is passed, and `overwrite: false` makes the write create-only \u2014 the refusal names the file, the line counts and both options. `batch` applies several edits, or one edit across several files, as ONE atomic call: every entry is planned before anything is written (so a refusal anywhere writes nothing), the bytes of EVERY target are re-checked together immediately before the first write, and formatting, validation and reindexing each run ONCE for the whole call. Two entries may not resolve to the same file \u2014 each is planned from its file's pre-edit bytes, so the second would discard the first, and that is refused by entry index. Every answer carries a per-file `envelope` (`planned`, `committed`, `restored`, and the pre/post byte counts), and a refused call carries the same envelope on the error, so \"is my working tree back?\" is answered per file rather than by one boolean. Unlike `write`, `batch` needs an index: the mode cannot promise of a list what a single entry's plan proves.",
295
+ inputSchema: { type: "object", properties: { mode: { type: "string", enum: ["write", "update", "insert", "delete", "refactor", "batch"], description: "Edit kind: write (create or overwrite a file), update (replace a symbol body), insert (add code relative to a symbol), delete (remove a symbol), refactor (identifier-aware rename), or batch (apply several edits, or one edit across several files, as one atomic call)" }, dryRun: { type: "boolean", description: "Preview the planned changes without writing any file (default false)" }, filePath: { type: "string", description: "write: project-relative target file to create or overwrite. Other modes: disambiguates symbolName when it appears in multiple files" }, symbolName: { type: "string", description: "Name of the symbol to update, delete, or refactor (the index must contain it; a target file changed since indexing is refreshed automatically before the edit)" }, lineStart: { type: "number", description: "Declaration line of the target symbol, used with symbolName/filePath to disambiguate repeated names" }, content: { type: "string", description: "Full file content for write mode; also accepted in place of newContent for update and insert" }, newContent: { type: "string", description: "Replacement source: update swaps the symbol body for this text; insert adds this text relative to targetSymbolName" }, targetSymbolName: { type: "string", description: "insert: symbol whose body or boundary receives the new code (required for insert)" }, position: { type: "string", enum: ["before", "after", "inside-top", "inside-bottom"], description: "insert anchor relative to targetSymbolName (default after)" }, newName: { type: "string", description: "refactor: the valid identifier the symbol is renamed to" }, force: { type: "boolean", description: "delete: allow removal when indexed callers or live references survive; leftovers are reported as warnings (default false)" }, format: { type: "boolean", description: "Run the project formatter on written files after the edit (default true)" }, overwrite: { type: "boolean", description: "write only. Omitted: replace the file, but refuse a replacement that removes at least half of a file's lines and at least 5 lines (a probable mistake, such as a body fragment sent to write instead of update). true: replace it whatever the size. false: create-only \u2014 refuse when the file already exists." }, edits: { type: "array", description: "batch only, required there: the edits to apply, each a complete request in one of the five single-file modes (write|update|insert|delete|refactor), e.g. [{mode:'update',symbolName:'helper',newContent:'...'},{mode:'delete',symbolName:'oldFlag'}]. Entries may not set dryRun or format (those belong to the batch, which previews, formats and reindexes once), may not nest a batch, and may not two of them resolve to the same file \u2014 each entry is planned from its file's pre-edit bytes, so a second entry on one file would discard the first (refused by entry index).", items: { type: "object", description: "one edit: `mode` (required: write|update|insert|delete|refactor) plus exactly the options THAT mode accepts at the top level \u2014 write takes filePath+content/newContent (+overwrite), update takes symbolName+newContent, insert takes targetSymbolName+newContent (+position), delete takes symbolName (+force), refactor takes symbolName+newName; every entry may also take filePath and lineStart to disambiguate. An option the entry's own mode would not accept at the top level is refused here too, by name, so an entry is validated by the same rule as the single-mode call it mirrors." } }, files: { type: "array", description: "batch only: the fan-out form \u2014 each entry applies once per listed project-relative path, and `files` supplies the filePath of every entry (so an entry with `files` must not name its own). Omit it to let each entry name its own target. The list may not repeat a path.", items: { type: "string" } } }, required: ["mode"] },
296
+ mutability: "write",
297
+ requiresIndex: true,
298
+ modes: ["write", "update", "insert", "delete", "refactor", "batch"],
299
+ // `write` is the one mode whose plan never consults the index, so it is the
300
+ // one exemption. `batch` is deliberately absent: a batch containing only
301
+ // `write` entries could run without an index, but the MODE cannot promise
302
+ // that of a caller-supplied list, so the exemption stays with the single mode
303
+ // whose plan provably never reads a row.
304
+ indexFreeModes: ["write"],
305
+ aliases: [],
306
+ deprecated: false,
307
+ documentation: { category: "operations", summary: "Unified safe source editing with preview, validation, rollback, and reindex.", defaults: { dryRun: false, format: true, force: false }, examples: [{ mode: "update", symbolName: "helper", newContent: "export function helper() { return 2 }" }, { mode: "insert", targetSymbolName: "main", position: "inside-top", newContent: "const started = true" }, { mode: "write", filePath: "src/new.ts", content: "export const value = 1" }] }
308
+ };
309
+
310
+ // src/mcp/tools/bb_index/descriptor.ts
311
+ var descriptor11 = {
312
+ name: "bb_index",
313
+ description: 'Reindex a file or directory so the index reflects edits immediately (agent-fresh). Accepts a path (file or dir); a file reindexes only that file, a dir runs an incremental index of that dir (changed files only). mode=post re-runs the heavy derived-data phases (relationships/enrichment/gravity/taints/health) WITHOUT re-parsing \u2014 the remediation after edits on incremental paths. Every mode requires `path`, and it MUST resolve inside this project: an absolute path elsewhere, a `../` traversal, a foreign directory and a symlink inside the project pointing out of it are all REFUSED, because an out-of-tree file would be stored with a `relative_path` that is not relative to anything and every scoped query, file name and freshness check for it would then be wrong. For mode=post the path must additionally NOT be a file: the phases are project-wide, so pass the project root (or a directory inside it). The report says what happened rather than only how much: `outcome` is `indexed`, `partial` or `no-op`; a no-op carries a `reason` that distinguishes "nothing changed: the file already matches the index (mtime, size and sha256 agree)" from "this file holds nothing the indexer extracts", and `unchanged`/`declined`/`skipped` say how many files fell into each case. When errors occurred, `errorDetail` names the file and the message beside the `errors` count, and `walCheckpointed` says whether the write WAL was reclaimed after the run. Paths in the report are PROJECT-RELATIVE, so the same request returns the same bytes on any machine. The counts are stable between identical calls; the `ms` fields are measured wall-clock and are expected to vary.',
314
+ inputSchema: {
315
+ type: "object",
316
+ properties: {
317
+ path: {
318
+ type: "string",
319
+ description: "Project-relative file (reindex only that file) or directory (incremental index of changed files); for mode=post, the project root or a directory inside it whose derived phases should be re-run (a file path is refused). REQUIRED in every mode and checked rather than ignored. Must resolve INSIDE the project: both sides are resolved through realpath, so a symlink inside the project pointing out of it is refused too."
320
+ },
321
+ mode: {
322
+ type: "string",
323
+ enum: ["auto", "file", "dir", "post"],
324
+ description: "auto (default) infers file vs dir from path; file reindexes one file (unchanged files answer no-op instead of being re-parsed); dir runs an incremental directory index; post re-runs derived phases (relationships/taints/health) without re-parsing"
325
+ }
326
+ },
327
+ required: ["path"]
328
+ },
329
+ mutability: "write",
330
+ requiresIndex: false,
331
+ modes: ["auto", "file", "dir", "post"],
332
+ aliases: [],
333
+ deprecated: false,
334
+ documentation: {
335
+ category: "operations",
336
+ summary: "Immediately refresh the index for a just-written/edited file or directory; mode=post re-derives relationships/taints/health without re-parsing.",
337
+ defaults: { mode: "auto" },
338
+ examples: [{ path: "src/new-file.ts" }, { path: "src", mode: "dir" }, { path: ".", mode: "post" }]
339
+ }
340
+ };
341
+
342
+ // src/mcp/tools/bb_graph/descriptor.ts
343
+ var descriptor12 = {
344
+ name: "bb_graph",
345
+ description: "Per-symbol dependency graph over the canonical graph engine: callers, callees, per-edge evidence tier (structural/import_based/usage_contextual/reference_qualified/inferred), the stored evidence class and resolution basis behind each tier, confidence, and cross-language edge count. Same engine and tier rules the editor graph panel uses; truncated traversals are declared with a reason, never silent. Traversal semantics: a relationship stored against any member of a class (a method call, an inherited behavior) is attributed to the owner class, so caller/callee sets include the owner-family level \u2014 every edge still traces to a stored relationship row and states whether it matched the symbol itself or one of its members. Each edge's `rowId` is the stored relationship key with its leading '<projectHash>:rel:' removed, and each endpoint's `id` is the stored symbol key with its leading '<projectHash>:' removed \u2014 both prefixes are constant within one answer, and `root.id` carries the FULL stored form, so '<projectHash>' is the segment before its first ':' and prepending it (plus ':rel:' for a rowId) reconstructs the store key exactly. An `ext:` id has no such prefix and is returned whole. The graph is READ from a read-only index handle: asking a question cannot mutate the index. Measured on this repository's reference index (11,440 files, 310,423 symbols, 820,040 stored relationships), a default call answers in 22-355 ms p95 depending on the symbol's degree, where the same call took 131 s before the engine's cascade and arm queries were rewritten to seek their indexes.",
346
+ inputSchema: {
347
+ type: "object",
348
+ properties: {
349
+ symbol: { type: "string", description: "symbol name to root the graph at (`target`/`symbolName`/`query` are accepted as aliases). Required; a blank name is refused as missing. An unknown name is refused after the resolver's declared containment fallback runs: that fallback is a substring scan, and on the 11,440-file reference index it costs about 5 s before answering not-found. It is a documented step of the resolver, not a silent budget breach; an exact-name miss does not run it." },
350
+ maxDepth: {
351
+ type: "number",
352
+ description: "traversal depth bound (default 5, max 10). Must be a whole number: a fractional depth is refused rather than floored."
353
+ },
354
+ direction: {
355
+ type: "string",
356
+ enum: ["callers", "callees", "both"],
357
+ description: `which side to traverse (default both). 'callers' answers "who depends on this" and skips the callee walk entirely \u2014 on a real index that is the difference between 1 edge and 37 in the same payload.`
358
+ },
359
+ relationshipTypes: {
360
+ type: "array",
361
+ items: { type: "string" },
362
+ description: "stored relationship kinds to include. Default: EVERY kind the index stores (calls, extends, implements, uses, instantiates, overrides, contains, references, imports, exports, decorates, renders, mixes_in, part_of) \u2014 deliberately not narrowed, because a narrower default silently drops a fifth of the graph and cannot descend a class hierarchy: 'contains' is the parent-to-member edge, so excluding it means a traversal rooted at a class cannot reach that class's own methods. The cost is size \u2014 the type-level 'uses'/'references'/'contains' rows are roughly half of what a typical index stores, so a hub symbol's default answer is 2.5-4.8x larger than a filtered one (measured: lodash 487 KiB against 101 KiB, ripgrep 795 against 321). Pass relationshipTypes: ['calls'] for the pure call graph, or ['implements'] to see only interface satisfaction. A kind you do not list is never reported; a filter you supply is a REQUEST, not a bound \u2014 it is reported in `summary.filterNarrowed` and `summary.filterReason` (never in `truncationReason`, which is reserved for bounds), and the kinds actually searched come back in `summary.scope.relationshipTypes`."
363
+ },
364
+ includeCrossLanguage: {
365
+ type: "boolean",
366
+ description: "include edges the indexer marked cross-language (default true). Set false for a single-language view; the rows removed BY REQUEST are counted in `summary.crossLanguageExcluded` per direction, so an empty set beside a non-zero count is a request, not an empty store."
367
+ },
368
+ timeBudgetMs: {
369
+ type: "number",
370
+ description: "wall-clock ceiling for the whole walk in milliseconds. DEFAULT 0 = NO clock bound: every other bound on this answer is a function of the DATA (edgeLimit, the 50-symbol per-direction fan-out, maxEdges, maxDepth), so the default answer is complete over them and byte-identical on a re-run. Pass a value only to cap latency, and know what you are buying: the ceiling is checked BETWEEN statements and never inside one \u2014 a SQLite statement cannot be interrupted \u2014 so a run that hits it returns a SAMPLE of the set. The sample is declared, never silent: `summary.timeBudgetExhausted` is true and `walk-time-budget-exhausted` is among `summary.truncationReasons`. There is no value that makes a clock-bounded answer reproducible; if you need the whole set, leave this unset"
371
+ },
372
+ maxEdges: {
373
+ type: "number",
374
+ description: 'hard cap on the number of edges in the WHOLE answer, across both directions and all depths (default 1000, max 5000; whole numbers only). `edgeLimit` bounds one query and this bounds the answer; their product is what a caller receives. Reaching it is declared as `total-edge-limit` with the value in `summary.maxEdges`. Raise it for a hub symbol, lower it for a quick shape check \u2014 a value below `edgeLimit` IS honored (the answer stops at it and says so), so `maxEdges` is the knob for "just show me the shape"'
375
+ },
376
+ edgeLimit: {
377
+ type: "number",
378
+ description: "Per-symbol call-site limit for one edge query (default 200, max 5000; whole numbers only). Reaching it is declared in `summary.truncated` with `summary.truncationReason` and the root's true count in `summary.callersIfUncapped`/`summary.calleesIfUncapped` \u2014 the limit is never a silent cap. Raise it to receive more sites in one answer."
379
+ }
380
+ },
381
+ required: ["symbol"]
382
+ },
383
+ mutability: "read",
384
+ requiresIndex: true,
385
+ modes: ["graph"],
386
+ aliases: [],
387
+ deprecated: false,
388
+ documentation: {
389
+ category: "graph",
390
+ summary: "Per-symbol graph with per-edge evidence tiers \u2014 the agent view of the editor's graph panel.",
391
+ defaults: { maxDepth: 5, direction: "both" },
392
+ examples: [
393
+ { symbol: "handleRequest" },
394
+ { symbol: "handleRequest", maxDepth: 8 },
395
+ { symbol: "handleRequest", direction: "callers" },
396
+ { symbol: "Handler", relationshipTypes: ["calls"] }
397
+ ]
398
+ }
399
+ };
400
+
401
+ // src/mcp/tools/bb_impact/descriptor.ts
402
+ var descriptor13 = {
403
+ // The agent door to bounded impact. Reuses src/lsp/impact.ts (the
404
+ // component the editor graph panel's impact mode uses) so agent and
405
+ // developer see identical totals and truncation.
406
+ name: "bb_impact",
407
+ description: "Bounded reverse-dependency impact for one symbol via the same component the editor graph panel uses: declared node budget (200) and depth (5), truncation as a first-class field with reason, and a declared total that is the size of the set the answer is a prefix of \u2014 measured, not inferred, so an answer that is short of the whole set says so even when no bound fired. Unknown symbols are loud declared errors \u2014 an empty impact set is never invented. Owner-cascade applies: impact includes dependents attributed at class level, not only exact-symbol rows. The relationship kinds that count are named in every answer's `rule.relationshipTypes` and can be overridden; a dependent carries its evidence tier, cross-language flag, bridge and position.",
408
+ inputSchema: {
409
+ type: "object",
410
+ properties: {
411
+ symbol: { type: "string", description: "symbol name to compute impact for (`target`/`symbolName`/`query` are accepted as aliases)" },
412
+ relationshipTypes: { type: "array", items: { type: "string" }, description: "stored relationship kinds to count as dependencies. Default: calls, instantiates, extends, implements. A kind that is not listed is never reported, and the applied list is echoed in `rule.relationshipTypes` on every answer. This is the same control bb_graph has, so a caller who wants the type-level `uses`/`references` rows can ask for them on either door." },
413
+ file: { type: "string", description: "optional defining-file hint for a symbol NAME that several stored symbols carry \u2014 the same server-resolved identity the editor's hover sends as `file`. Accept the stored project-relative path, or an absolute path ending in it. It must identify the symbol: a hint that does not is REFUSED with a named error, never silently ignored and never silently relabelled to another same-named copy. Ambiguous MEMBER names (constructors, members outside the graph's root types) resolve through their owning declaration at the hinted file; for a traversable name the walk roots where the graph engine resolves it, and a hint naming another copy is refused rather than answered. Omit it for unique names." },
414
+ refresh: { type: "boolean", description: "set false to skip the bounded staleness repair and answer from the existing index. When omitted, stale files inside the answer are repaired and the walk is re-run so the answer is not built from the rows the refresh replaced." }
415
+ },
416
+ required: ["symbol"]
417
+ },
418
+ mutability: "read",
419
+ requiresIndex: true,
420
+ modes: ["impact"],
421
+ aliases: [],
422
+ deprecated: false,
423
+ documentation: { category: "graph", summary: "Bounded impact with declared truncation \u2014 identical numbers to the editor's impact mode.", defaults: {}, examples: [{ symbol: "handleRequest" }] }
424
+ };
425
+
426
+ // src/mcp/tools/bb_cross_language/descriptor.ts
427
+ var descriptor14 = {
428
+ name: "bb_cross_language",
429
+ description: "Cross-language facts for one symbol from stored brain_relationship rows only (is_cross_language = 1), per-pair with bridge mechanism and evidence tier. Every fact names both ends \u2014 the copy of the requested name it came from and the other symbol's name, file, language and line \u2014 plus the stored row id. A name is many symbols (the index holds one per file/name/line), so facts are the union over every copy; `copies` names them and `ambiguous` says so. SEPARATELY, `memberFacts` holds the rows on the copies' MEMBERS: each fact's `self` is the MEMBER the stored row names, `owner` is the copy of the asked name it was attributed through, and `pair` is exactly `self.language \u2192 other.language` (null when the two are equal or unknown, and then the row is counted in `unpairableRowCount` rather than given a pair). Member rows are the MEMBER's edges, never absorbed into the symbol's \u2014 `ownFactCount`, `memberFactCount` and `silentItselfButNotItsMembers` tell the two apart. Absence is a property of the store: it is declared only when neither the copies nor their direct members hold a stored row. A row that exists but cannot name a language pair is NOT absence \u2014 it is counted in `unpairableRowCount` and named in `pairlessRowNote`, while `storedRowCount` still reports it. Rows on descendants at depth 2 or deeper are counted in `deeperDescendantRowCount` (both directions) and deliberately not cascaded. Counts name what they count: `factCount` facts in this answer, `pairCount` DISTINCT language pairs, `signatureCount` distinct (pair, direction, bridge, tier); `memberCount` is the distinct members that contributed BEFORE the limit while `memberFactCount` is what was shown, and `ownFactsTruncated`/`memberFactsTruncated` say which section a limit cut. The answer is bounded by `limit` (default 200, max 1000) with `truncated` and `totalCountIfKnown` declared, and the copy list has its own declared cap. This tool never infers, guesses, or aspires. Same source and laws as the editor's cross-language hover section.",
430
+ inputSchema: {
431
+ type: "object",
432
+ properties: {
433
+ symbol: { type: "string", description: "symbol name to fetch cross-language facts for (`target`/`symbolName`/`query` are accepted as aliases)" },
434
+ limit: {
435
+ type: "number",
436
+ description: "max facts to return (default 200, max 1000); truncation is declared, never silent. Refused below 1 or above 1000 rather than clamped",
437
+ default: 200,
438
+ minimum: 1,
439
+ maximum: 1e3
440
+ }
441
+ },
442
+ required: ["symbol"]
443
+ },
444
+ mutability: "read",
445
+ requiresIndex: true,
446
+ modes: ["cross-language"],
447
+ aliases: [],
448
+ deprecated: false,
449
+ documentation: {
450
+ category: "graph",
451
+ summary: "Stored cross-language pairs, each naming both ends, with member rows reported separately as the member's, absence declared as a property of the store, and declared bounds and truncation.",
452
+ defaults: { limit: 200 },
453
+ examples: [{ symbol: "Warehouse" }, { symbol: "Iterate" }, { symbol: "invoke", limit: 50 }]
454
+ }
455
+ };
456
+
457
+ // src/mcp/tools/bb_annotate/descriptor.ts
458
+ var descriptor15 = {
459
+ name: "bb_annotate",
460
+ description: "Attach an agent-authored annotation to a symbol in the shared index. Provenance is enforced: `author_class` is hardcoded 'agent' for tool writes (a tool call cannot write a 'human' note), and the row records the caller's self-declared `toolIdentity` plus timestamps. Re-annotating the same symbol+path updates the note in place and reports the note it replaced \u2014 one note per author per location is enforced by a unique index over (project, symbol, author, path). The symbol is not checked against the index; the reply MEASURES whether an editor surface can show the note and says so in `surfacing`. `editor` is the JOINT truth the hover and graph badge use: the name (or qualified name) must be indexed AND declared in the resolved file, so a name indexed only in another file is reported `editor: false` with the reason rather than promised. `path` resolves to the index's own spelling, so `src/a.ts`, `./src/a.ts` and the absolute path are the same note. Declared bounds: body 2000 chars, toolIdentity 200 chars, symbol 500 chars, path 1024 chars \u2014 refusals, never truncation. Annotations are opinions layered on evidence, never index facts. On a project with no index, the first write creates that project's annotation store and `surfacing.editor` is false. To change source code use bb_edit; to read notes back use bb_annotations.",
461
+ inputSchema: {
462
+ type: "object",
463
+ properties: {
464
+ symbol: { type: "string", description: "symbol name to annotate, max 500 chars (`target`/`query` are accepted as aliases); it is not required to exist in the index \u2014 reachability is measured and reported" },
465
+ body: { type: "string", description: "annotation text, max 2000 chars counted in code points (an astral glyph is one character); empty or whitespace-only is refused" },
466
+ path: { type: "string", description: "optional file path to scope the note to one file's copy of the symbol, max 1024 chars; resolved to the index's own spelling (`brain_file.relative_path`), so `src/a.ts`, `./src/a.ts` and the absolute path all reach the same note" },
467
+ toolIdentity: { type: "string", description: "optional name of the calling agent/tool, max 200 chars (recorded verbatim; line breaks and control characters are refused)" }
468
+ },
469
+ required: ["symbol", "body"]
470
+ },
471
+ mutability: "write",
472
+ requiresIndex: false,
473
+ modes: ["annotate"],
474
+ aliases: [],
475
+ deprecated: false,
476
+ documentation: {
477
+ category: "annotations",
478
+ summary: "Agent note with enforced provenance, stored where the human reads.",
479
+ defaults: {},
480
+ examples: [{ symbol: "handleRequest", body: "Legacy auth path \u2014 scheduled for removal in Q3." }]
481
+ }
482
+ };
483
+
484
+ // src/mcp/tools/bb_annotations/descriptor.ts
485
+ var descriptor16 = {
486
+ name: "bb_annotations",
487
+ description: "Read annotations for a symbol, one file's copy of a symbol (optional `path`), or all annotations up to the declared limit, with full provenance on every row: author class, tool identity, and timestamps. Every answer declares its bound: `count` is the page size, `totalCountIfKnown` the true total, and `truncated`/`truncationReason` fire when the limit actually dropped rows \u2014 a short page is never mistaken for a complete one, and at the declared cap the reason names narrowing the filter instead of an impossible limit raise. A `symbol` matches notes written under either name form the index publishes \u2014 the symbol's simple name AND its qualified name \u2014 the same pair the editor hover matches, so a note written as `render` is found by `Widget.render` and the other way round. A `path` is resolved to the index's own spelling before filtering. A project with no store yet is a declared absence; this read creates nothing. To write a note use bb_annotate.",
488
+ inputSchema: {
489
+ type: "object",
490
+ properties: {
491
+ symbol: {
492
+ type: "string",
493
+ description: "symbol name, simple name or qualified name; both forms resolve to the same notes. Omit to read the whole project. `target`/`query` are accepted as aliases"
494
+ },
495
+ path: {
496
+ type: "string",
497
+ description: "optional file path; resolved to the index's own spelling first, so `src/a.ts`, `./src/a.ts` and the absolute path are the same filter. A path without a symbol returns that file's own notes only \u2014 pass `includeNameLevel` for the project's name-level notes as well. Combined with a symbol, it returns that file's copy of the symbol plus name-level notes about that symbol. Omit to read every file for the symbol"
498
+ },
499
+ limit: {
500
+ type: "number",
501
+ description: "max rows returned (default 50, min 1, max 200); a truncated answer states the true total and a reason",
502
+ default: 50,
503
+ minimum: 1,
504
+ maximum: 200
505
+ },
506
+ includeNameLevel: {
507
+ type: "boolean",
508
+ description: "with `path` and no `symbol`, also return name-level notes (written with no path), which apply wherever their name appears. Off by default, so a path filter answers the question as asked rather than mixing in the whole project. With a symbol present the flag has no effect: name-level notes about that symbol always apply"
509
+ }
510
+ },
511
+ required: []
512
+ },
513
+ mutability: "read",
514
+ // The store is not the code index: absence is declared for a project that has
515
+ // no store, and the read never creates one.
516
+ requiresIndex: false,
517
+ modes: ["annotate"],
518
+ aliases: [],
519
+ deprecated: false,
520
+ documentation: {
521
+ category: "annotations",
522
+ summary: "Annotations with full provenance; declared absence; both name forms matched; path canonicalized.",
523
+ defaults: { limit: 50 },
524
+ examples: [{ symbol: "handleRequest", path: "src/server.ts" }, { symbol: "Widget.render" }, { includeNameLevel: true, path: "src/index.ts" }]
525
+ }
526
+ };
527
+
528
+ // src/mcp/tool-registry.ts
529
+ var descriptors = [
530
+ descriptor,
531
+ descriptor2,
532
+ descriptor3,
533
+ descriptor4,
534
+ descriptor5,
535
+ descriptor6,
536
+ descriptor7,
537
+ descriptor8,
538
+ descriptor9,
539
+ descriptor10,
540
+ descriptor11,
541
+ descriptor12,
542
+ descriptor13,
543
+ descriptor14,
544
+ descriptor15,
545
+ descriptor16
546
+ ];
547
+ var TOOL_DESCRIPTORS = descriptors;
548
+ var PUBLIC_TOOL_DESCRIPTORS = descriptors;
549
+ function validateToolDescriptors(items) {
550
+ const names = /* @__PURE__ */ new Set();
551
+ for (const item of items) {
552
+ if (!item.name || names.has(item.name)) throw new Error(`duplicate or empty tool name: ${item.name || "<empty>"}`);
553
+ names.add(item.name);
554
+ if (item.inputSchema.type !== "object") throw new Error(`${item.name}: input schema must be an object`);
555
+ const properties = item.inputSchema.properties ?? {};
556
+ for (const required of item.inputSchema.required ?? []) {
557
+ if (!(required in properties)) throw new Error(`${item.name}: required field ${required} is not declared`);
558
+ }
559
+ if (new Set(item.modes).size !== item.modes.length) throw new Error(`${item.name}: duplicate modes`);
560
+ if (item.plannedModes) {
561
+ if (new Set(item.plannedModes).size !== item.plannedModes.length) throw new Error(`${item.name}: duplicate planned modes`);
562
+ const modeSet = new Set(item.modes);
563
+ const overlap = item.plannedModes.filter((mode) => modeSet.has(mode));
564
+ if (overlap.length) throw new Error(`${item.name}: planned modes overlap executable modes: ${overlap.join(", ")}`);
565
+ }
566
+ if (item.freshnessModes) {
567
+ if (new Set(item.freshnessModes).size !== item.freshnessModes.length) throw new Error(`${item.name}: duplicate freshness modes`);
568
+ const modeSet = new Set(item.modes);
569
+ const unknown = item.freshnessModes.filter((mode) => !modeSet.has(mode));
570
+ if (unknown.length) throw new Error(`${item.name}: freshness modes not in executable modes: ${unknown.join(", ")}`);
571
+ }
572
+ if (item.indexFreeModes) {
573
+ if (new Set(item.indexFreeModes).size !== item.indexFreeModes.length) throw new Error(`${item.name}: duplicate index-free modes`);
574
+ const modeSet = new Set(item.modes);
575
+ const unknown = item.indexFreeModes.filter((mode) => !modeSet.has(mode));
576
+ if (unknown.length) throw new Error(`${item.name}: index-free modes not in executable modes: ${unknown.join(", ")}`);
577
+ if (!item.requiresIndex) throw new Error(`${item.name}: indexFreeModes is meaningless when requiresIndex is false`);
578
+ }
579
+ if (new Set(item.aliases).size !== item.aliases.length) throw new Error(`${item.name}: duplicate aliases`);
580
+ if (item.deprecated && !item.replacement) throw new Error(`${item.name}: deprecated tools require a replacement`);
581
+ }
582
+ }
583
+ validateToolDescriptors(TOOL_DESCRIPTORS);
584
+ function deriveToolContracts(items = TOOL_DESCRIPTORS) {
585
+ return items.map((descriptor17) => ({
586
+ name: descriptor17.name,
587
+ mutability: descriptor17.mutability,
588
+ requiresIndex: descriptor17.requiresIndex,
589
+ indexFreeModes: descriptor17.indexFreeModes ?? [],
590
+ modes: descriptor17.modes,
591
+ plannedModes: descriptor17.plannedModes ?? [],
592
+ required: descriptor17.inputSchema.required ?? [],
593
+ properties: Object.keys(descriptor17.inputSchema.properties ?? {}),
594
+ defaults: descriptor17.documentation.defaults,
595
+ output: "text",
596
+ replacement: descriptor17.name
597
+ }));
598
+ }
599
+ function toMcpToolList(items = TOOL_DESCRIPTORS) {
600
+ return items.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
601
+ }
602
+ function serializeToolMetadata(items = TOOL_DESCRIPTORS) {
603
+ return JSON.stringify(items, null, 2);
604
+ }
605
+
606
+ export {
607
+ TOOL_DESCRIPTORS,
608
+ PUBLIC_TOOL_DESCRIPTORS,
609
+ validateToolDescriptors,
610
+ deriveToolContracts,
611
+ toMcpToolList,
612
+ serializeToolMetadata
613
+ };