@backbencher/brain 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/EMBEDDINGS.md +355 -0
  3. package/LICENSE +21 -0
  4. package/README.md +415 -2
  5. package/bin/bb +13 -0
  6. package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
  7. package/dist/brain-index-health-Q5APTJQL.js +17 -0
  8. package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
  9. package/dist/brain.sql-EIM4WS4D.js +29 -0
  10. package/dist/chunk-22V37YTK.js +1758 -0
  11. package/dist/chunk-2BSKD7O3.js +40 -0
  12. package/dist/chunk-2DE27DVC.js +1314 -0
  13. package/dist/chunk-2RRBMNBQ.js +322 -0
  14. package/dist/chunk-2WXYNLBA.js +29 -0
  15. package/dist/chunk-3CTFIJJB.js +17 -0
  16. package/dist/chunk-3ZTOHHSW.js +2104 -0
  17. package/dist/chunk-4H22TLOT.js +543 -0
  18. package/dist/chunk-5BVAH26D.js +340 -0
  19. package/dist/chunk-62DHLYW6.js +3427 -0
  20. package/dist/chunk-7AN7U5IZ.js +371 -0
  21. package/dist/chunk-7DRPNVIN.js +782 -0
  22. package/dist/chunk-7MBYT777.js +174 -0
  23. package/dist/chunk-AHDWGM7Q.js +236 -0
  24. package/dist/chunk-B5GEAAYF.js +818 -0
  25. package/dist/chunk-BEIZZ73B.js +1479 -0
  26. package/dist/chunk-BQ3OVQ3P.js +125 -0
  27. package/dist/chunk-D2Q4Y47A.js +53 -0
  28. package/dist/chunk-DIYAGQJL.js +34 -0
  29. package/dist/chunk-DWKXZ6IF.js +336 -0
  30. package/dist/chunk-DYM4FQEE.js +90 -0
  31. package/dist/chunk-ENW4BTBA.js +930 -0
  32. package/dist/chunk-EWMWK7WN.js +75 -0
  33. package/dist/chunk-EY5ZZUUA.js +626 -0
  34. package/dist/chunk-GBI7V7KY.js +69 -0
  35. package/dist/chunk-GEFFO6O2.js +152 -0
  36. package/dist/chunk-HJEWO5EV.js +247 -0
  37. package/dist/chunk-HQSPJDHI.js +613 -0
  38. package/dist/chunk-KAET47ZL.js +125 -0
  39. package/dist/chunk-KCDECRJY.js +440 -0
  40. package/dist/chunk-L5TDYKYK.js +76 -0
  41. package/dist/chunk-MLKGABMK.js +9 -0
  42. package/dist/chunk-NO3U3FJQ.js +201 -0
  43. package/dist/chunk-OWISBAOF.js +61 -0
  44. package/dist/chunk-QKTRECYS.js +1119 -0
  45. package/dist/chunk-QLK6ED4Q.js +235 -0
  46. package/dist/chunk-R2GW4F6R.js +19 -0
  47. package/dist/chunk-RCMTFSEF.js +87 -0
  48. package/dist/chunk-RM3PY6TG.js +376 -0
  49. package/dist/chunk-ROMM6KMC.js +433 -0
  50. package/dist/chunk-SGGCSHAX.js +12050 -0
  51. package/dist/chunk-SK6YVHFZ.js +211 -0
  52. package/dist/chunk-SM7RFQRO.js +214 -0
  53. package/dist/chunk-UGR4QIGS.js +67 -0
  54. package/dist/chunk-UQNQZXHY.js +711 -0
  55. package/dist/chunk-UUNHHX4M.js +13 -0
  56. package/dist/chunk-VL52PL2T.js +71 -0
  57. package/dist/chunk-WIG36E67.js +196 -0
  58. package/dist/chunk-XHK3WWGB.js +7436 -0
  59. package/dist/chunk-XTQM2IP3.js +2863 -0
  60. package/dist/chunk-ZE2LG76D.js +1296 -0
  61. package/dist/chunk-ZETTGPAW.js +6950 -0
  62. package/dist/cli/index.js +37 -0
  63. package/dist/config-HZAAYDP3.js +10 -0
  64. package/dist/content-search-7XIP555Q.js +593 -0
  65. package/dist/database-76RP44JG.js +9 -0
  66. package/dist/doctor-ANGETYMN.js +395 -0
  67. package/dist/edit-engine-CG72LDZD.js +51 -0
  68. package/dist/engine-BKEOGN7N.js +11 -0
  69. package/dist/engine-VIW6NSKO.js +18 -0
  70. package/dist/fingerprint-OBLIMZLP.js +33 -0
  71. package/dist/framework-intelligence-3LORTPOX.js +860 -0
  72. package/dist/freshness-3K6VU5OT.js +174 -0
  73. package/dist/graph-engine-HBSQNPTB.js +21 -0
  74. package/dist/graph_request-Q6NKB6N2.js +350 -0
  75. package/dist/impact_request-LYP672WE.js +68 -0
  76. package/dist/index-space-IUUN5WYV.js +15 -0
  77. package/dist/index-status-7OVGYSGW.js +34 -0
  78. package/dist/index.js +159 -0
  79. package/dist/instance-FO6QZRLH.js +19 -0
  80. package/dist/language-detection-DFCAU5DZ.js +11 -0
  81. package/dist/lsp/server.js +4301 -0
  82. package/dist/mcp/server.js +132 -0
  83. package/dist/onboard-DIQZV2NW.js +161 -0
  84. package/dist/override-companion-pass-XR7MHTP7.js +117 -0
  85. package/dist/parallel-indexer-FK2XKZXY.js +88 -0
  86. package/dist/pool-worker.js +1153 -0
  87. package/dist/project-health-6XPENVAA.js +94 -0
  88. package/dist/project-index-5SDPHITE.js +57 -0
  89. package/dist/project-registry-ZJ3DVUGO.js +355 -0
  90. package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
  91. package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
  92. package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
  93. package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
  94. package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
  95. package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
  96. package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
  97. package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
  98. package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
  99. package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
  100. package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
  101. package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
  102. package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
  103. package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
  104. package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
  105. package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
  106. package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
  107. package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
  108. package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
  109. package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
  110. package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
  111. package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
  112. package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
  113. package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
  114. package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
  115. package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
  116. package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
  117. package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
  118. package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
  119. package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
  120. package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
  121. package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
  122. package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
  123. package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
  124. package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
  125. package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
  126. package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
  127. package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
  128. package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
  129. package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
  130. package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
  131. package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
  132. package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
  133. package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
  134. package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
  135. package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
  136. package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
  137. package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
  138. package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
  139. package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
  140. package/dist/regex-search-NJKHMZNO.js +239 -0
  141. package/dist/rules-ACHPB7QU.js +152 -0
  142. package/dist/scoped-path-MJYWJZQX.js +11 -0
  143. package/dist/setup-show-NT5ZHYNE.js +15 -0
  144. package/dist/setup-wizard-G5YYR5T6.js +24 -0
  145. package/dist/symbol-search-GZJIM2J2.js +16 -0
  146. package/dist/taint-KXUHHPIE.js +202 -0
  147. package/dist/taint-analyzer-N7YLZU3N.js +163 -0
  148. package/dist/tool-handlers-OJDC35DI.js +72 -0
  149. package/dist/tool-registry-BPEJNSZG.js +17 -0
  150. package/dist/tool-validation-Q57YMMBX.js +32 -0
  151. package/dist/type-inference-QMT2RMLV.js +13 -0
  152. package/dist/vector-binary-LMY4QGAN.js +19 -0
  153. package/dist/version-ABP2TUL2.js +9 -0
  154. package/dist/wasm-NMWO3WXY.js +16 -0
  155. package/docs/EMBEDDINGS.md +132 -0
  156. package/docs/README.md +178 -0
  157. package/docs/data/m10-baseline.json +602 -0
  158. package/docs/data/m10-benchmark-baseline.json +57 -0
  159. package/docs/data/m10-benchmark-last-run.json +127 -0
  160. package/docs/data/m10-final-audit.json +215 -0
  161. package/docs/data/m12-final-audit.json +138 -0
  162. package/docs/data/m12-surface-matrix.json +50 -0
  163. package/docs/data/public-tools.json +1902 -0
  164. package/docs/indexing/DEAD-ASSETS.md +88 -0
  165. package/docs/indexing/INDEXER-REVIEW.md +634 -0
  166. package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
  167. package/docs/indexing/README.md +427 -0
  168. package/docs/indexing/discovery-matrix.md +41 -0
  169. package/docs/indexing/embeddings.md +325 -0
  170. package/docs/indexing/environment-variables.md +77 -0
  171. package/docs/indexing/performance.md +323 -0
  172. package/docs/indexing/resume.md +345 -0
  173. package/docs/indexing/troubleshooting.md +391 -0
  174. package/docs/man/bb.1 +190 -0
  175. package/docs/tools/bb_analyze.md +147 -0
  176. package/docs/tools/bb_annotate.md +110 -0
  177. package/docs/tools/bb_annotations.md +106 -0
  178. package/docs/tools/bb_ask.md +101 -0
  179. package/docs/tools/bb_cross_language.md +103 -0
  180. package/docs/tools/bb_doc.md +169 -0
  181. package/docs/tools/bb_edit.md +185 -0
  182. package/docs/tools/bb_graph.md +169 -0
  183. package/docs/tools/bb_health.md +156 -0
  184. package/docs/tools/bb_impact.md +117 -0
  185. package/docs/tools/bb_index.md +108 -0
  186. package/docs/tools/bb_migrate.md +135 -0
  187. package/docs/tools/bb_patterns.md +140 -0
  188. package/docs/tools/bb_search.md +150 -0
  189. package/docs/tools/bb_security.md +100 -0
  190. package/docs/tools/bb_visualize.md +184 -0
  191. package/grammars/README.md +57 -0
  192. package/grammars/THIRD-PARTY-NOTICES.md +50 -0
  193. package/grammars/tree-sitter-c/LICENSE +21 -0
  194. package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
  195. package/grammars/tree-sitter-cpp/LICENSE +21 -0
  196. package/grammars/tree-sitter-csharp/LICENSE +21 -0
  197. package/grammars/tree-sitter-css/LICENSE +21 -0
  198. package/grammars/tree-sitter-css/README.md +21 -0
  199. package/grammars/tree-sitter-dart/LICENSE +20 -0
  200. package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
  201. package/grammars/tree-sitter-dart-upstream/README.md +18 -0
  202. package/grammars/tree-sitter-elixir/LICENSE +176 -0
  203. package/grammars/tree-sitter-elixir/README.md +11 -0
  204. package/grammars/tree-sitter-fsharp/LICENSE +21 -0
  205. package/grammars/tree-sitter-fsharp/README.md +45 -0
  206. package/grammars/tree-sitter-graphql/LICENSE +21 -0
  207. package/grammars/tree-sitter-graphql/README.md +106 -0
  208. package/grammars/tree-sitter-groovy/LICENSE +21 -0
  209. package/grammars/tree-sitter-groovy/README.md +4 -0
  210. package/grammars/tree-sitter-haskell/LICENSE +21 -0
  211. package/grammars/tree-sitter-haskell/README.md +394 -0
  212. package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
  213. package/grammars/tree-sitter-hcl/LICENSE +201 -0
  214. package/grammars/tree-sitter-hcl/README.md +39 -0
  215. package/grammars/tree-sitter-html/LICENSE +21 -0
  216. package/grammars/tree-sitter-html/README.md +21 -0
  217. package/grammars/tree-sitter-julia/LICENSE +21 -0
  218. package/grammars/tree-sitter-julia/README.md +24 -0
  219. package/grammars/tree-sitter-kotlin/LICENSE +9 -0
  220. package/grammars/tree-sitter-lua/LICENSE.md +21 -0
  221. package/grammars/tree-sitter-lua/README.md +29 -0
  222. package/grammars/tree-sitter-make/LICENSE +21 -0
  223. package/grammars/tree-sitter-matlab/LICENSE +19 -0
  224. package/grammars/tree-sitter-matlab/README.md +99 -0
  225. package/grammars/tree-sitter-nim/LICENSE +373 -0
  226. package/grammars/tree-sitter-nim/README.md +46 -0
  227. package/grammars/tree-sitter-objc/LICENSE +21 -0
  228. package/grammars/tree-sitter-objc/README.md +20 -0
  229. package/grammars/tree-sitter-perl/LICENSE +7 -0
  230. package/grammars/tree-sitter-perl/README.md +100 -0
  231. package/grammars/tree-sitter-powershell/LICENSE +21 -0
  232. package/grammars/tree-sitter-powershell/README.md +7 -0
  233. package/grammars/tree-sitter-proto/LICENSE +21 -0
  234. package/grammars/tree-sitter-proto/README.md +105 -0
  235. package/grammars/tree-sitter-r/LICENSE +9 -0
  236. package/grammars/tree-sitter-r/README.md +66 -0
  237. package/grammars/tree-sitter-ruby/LICENSE +22 -0
  238. package/grammars/tree-sitter-scss/LICENSE +21 -0
  239. package/grammars/tree-sitter-scss/README.md +17 -0
  240. package/grammars/tree-sitter-solidity/LICENSE +7 -0
  241. package/grammars/tree-sitter-sql/LICENSE +21 -0
  242. package/grammars/tree-sitter-typescript/LICENSE +21 -0
  243. package/grammars/tree-sitter-vba/LICENSE +21 -0
  244. package/grammars/tree-sitter-vba/README.md +738 -0
  245. package/grammars/tree-sitter-vbnet/LICENSE +21 -0
  246. package/grammars/tree-sitter-vbnet/README.md +322 -0
  247. package/grammars/tree-sitter-xml/LICENSE +19 -0
  248. package/grammars/tree-sitter-yaml/LICENSE +20 -0
  249. package/grammars/tree-sitter-yaml/README.md +21 -0
  250. package/package.json +184 -4
  251. package/wasm/dart.wasm +0 -0
  252. package/wasm/tree-sitter-bash.wasm +0 -0
  253. package/wasm/tree-sitter-c.wasm +0 -0
  254. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  255. package/wasm/tree-sitter-clean.wasm +0 -0
  256. package/wasm/tree-sitter-clojure.wasm +0 -0
  257. package/wasm/tree-sitter-core_schema.wasm +0 -0
  258. package/wasm/tree-sitter-cpp.wasm +0 -0
  259. package/wasm/tree-sitter-css.wasm +0 -0
  260. package/wasm/tree-sitter-dart.wasm +0 -0
  261. package/wasm/tree-sitter-elisp.wasm +0 -0
  262. package/wasm/tree-sitter-elixir.wasm +0 -0
  263. package/wasm/tree-sitter-elm.wasm +0 -0
  264. package/wasm/tree-sitter-embedded_template.wasm +0 -0
  265. package/wasm/tree-sitter-erlang.wasm +0 -0
  266. package/wasm/tree-sitter-fsharp.wasm +0 -0
  267. package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
  268. package/wasm/tree-sitter-go.wasm +0 -0
  269. package/wasm/tree-sitter-graphql.wasm +0 -0
  270. package/wasm/tree-sitter-groovy.wasm +0 -0
  271. package/wasm/tree-sitter-haskell.wasm +0 -0
  272. package/wasm/tree-sitter-hcl.wasm +0 -0
  273. package/wasm/tree-sitter-html.wasm +0 -0
  274. package/wasm/tree-sitter-java.wasm +0 -0
  275. package/wasm/tree-sitter-javascript.wasm +0 -0
  276. package/wasm/tree-sitter-json.wasm +0 -0
  277. package/wasm/tree-sitter-json_schema.wasm +0 -0
  278. package/wasm/tree-sitter-julia.wasm +0 -0
  279. package/wasm/tree-sitter-kotlin.wasm +0 -0
  280. package/wasm/tree-sitter-lua.wasm +0 -0
  281. package/wasm/tree-sitter-make.wasm +0 -0
  282. package/wasm/tree-sitter-markdown.wasm +0 -0
  283. package/wasm/tree-sitter-markdown_inline.wasm +0 -0
  284. package/wasm/tree-sitter-matlab.wasm +0 -0
  285. package/wasm/tree-sitter-nim.wasm +0 -0
  286. package/wasm/tree-sitter-objc.wasm +0 -0
  287. package/wasm/tree-sitter-ocaml.wasm +0 -0
  288. package/wasm/tree-sitter-perl.wasm +0 -0
  289. package/wasm/tree-sitter-php.wasm +0 -0
  290. package/wasm/tree-sitter-php_only.wasm +0 -0
  291. package/wasm/tree-sitter-powershell.wasm +0 -0
  292. package/wasm/tree-sitter-protobuf.wasm +0 -0
  293. package/wasm/tree-sitter-python.wasm +0 -0
  294. package/wasm/tree-sitter-ql.wasm +0 -0
  295. package/wasm/tree-sitter-r.wasm +0 -0
  296. package/wasm/tree-sitter-rescript.wasm +0 -0
  297. package/wasm/tree-sitter-ruby.wasm +0 -0
  298. package/wasm/tree-sitter-rust.wasm +0 -0
  299. package/wasm/tree-sitter-scala.wasm +0 -0
  300. package/wasm/tree-sitter-scss.wasm +0 -0
  301. package/wasm/tree-sitter-solidity.wasm +0 -0
  302. package/wasm/tree-sitter-sql.wasm +0 -0
  303. package/wasm/tree-sitter-svelte.wasm +0 -0
  304. package/wasm/tree-sitter-swift.wasm +0 -0
  305. package/wasm/tree-sitter-systemrdl.wasm +0 -0
  306. package/wasm/tree-sitter-terraform.wasm +0 -0
  307. package/wasm/tree-sitter-tlaplus.wasm +0 -0
  308. package/wasm/tree-sitter-toml.wasm +0 -0
  309. package/wasm/tree-sitter-tsx.wasm +0 -0
  310. package/wasm/tree-sitter-typescript.wasm +0 -0
  311. package/wasm/tree-sitter-vba.wasm +0 -0
  312. package/wasm/tree-sitter-vbnet.wasm +0 -0
  313. package/wasm/tree-sitter-vue.wasm +0 -0
  314. package/wasm/tree-sitter-xml.wasm +0 -0
  315. package/wasm/tree-sitter-yaml.wasm +0 -0
  316. package/wasm/tree-sitter-zig.wasm +0 -0
  317. package/wasm/tree-sitter.wasm +0 -0
  318. package/wasm/tsx.wasm +0 -0
  319. package/wasm/typescript.wasm +0 -0
@@ -0,0 +1,147 @@
1
+ <!--
2
+ Author: Muhammad Ishaq Khan
3
+
4
+ WHAT THIS DOCUMENT IS — the generated reference for bb_analyze: contract, input schema, returns, examples, errors, limitations, performance, safety and evidence semantics.
5
+ WHO IT IS FOR — callers of bb_analyze through any MCP host, the CLI (`bb tool bb_analyze`), or a library.
6
+ EDIT TOGETHER WHEN THIS CHANGES — src/mcp/tools/bb_analyze/descriptor.ts, src/mcp/tools/bb_analyze/handler.ts, script/tool-docs/curation/bb_analyze.ts; regenerate with `npx tsx script/verify-tool-docs.ts --only bb_analyze --write`.
7
+ -->
8
+ # `bb_analyze`
9
+
10
+ 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 — a relationship stored against a class member is attributed to the owner class — 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 — `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.
11
+
12
+ ## Contract
13
+
14
+ - Category: `analysis`
15
+ - Mutability: `read`
16
+ - Requires index: `true`
17
+ - Modes: `callers`, `callees`, `trace`, `dependencies`, `stats`, `complexity`, `impact`, `test_mapping`, `test_gaps`, `test_impact`, `test_quality`
18
+ - Planned modes (roadmap, not executable): (none)
19
+ - Defaults: `{"limit":"25 edges per direction (callers/callees/trace); 20 files (dependencies); 50 entries (test_mapping/test_gaps); 50 files (test_quality)","target":"required for callers, callees, trace, impact, test_impact; optional file path or directory prefix for complexity","filePath":"optional path prefix (or an exact file) for dependencies, test_mapping, test_gaps, test_quality"}`
20
+
21
+ ## Input Schema
22
+
23
+ ```json
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "mode": {
28
+ "type": "string",
29
+ "enum": [
30
+ "callers",
31
+ "callees",
32
+ "trace",
33
+ "dependencies",
34
+ "stats",
35
+ "complexity",
36
+ "impact",
37
+ "test_mapping",
38
+ "test_gaps",
39
+ "test_impact",
40
+ "test_quality"
41
+ ],
42
+ "description": "analysis mode"
43
+ },
44
+ "target": {
45
+ "type": "string",
46
+ "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 — the search always uses the whole string."
47
+ },
48
+ "filePath": {
49
+ "type": "string",
50
+ "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`."
51
+ },
52
+ "limit": {
53
+ "type": "number",
54
+ "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."
55
+ }
56
+ },
57
+ "required": [
58
+ "mode"
59
+ ]
60
+ }
61
+ ```
62
+
63
+ ## Returns
64
+
65
+ - Mode-specific typed analysis: graph edges, file dependency edges, project counts, complexity metrics, impact, or test evidence
66
+ - A declared bound on every answer — what was returned, what exists, and whether the set is the whole one
67
+ - Explicit unavailable states: a metric or target that cannot be computed is `available: false` / `notFoundReason`, never a substituted number
68
+
69
+ ## Examples
70
+
71
+ `{"mode":"callers","target":"handleRequest"}`
72
+
73
+ `{"mode":"complexity","target":"src/api"}`
74
+
75
+ `{"mode":"test_quality"}`
76
+
77
+ `{"mode":"dependencies","filePath":"src/core"}`
78
+
79
+ `{"mode":"impact","target":"handleRequest","limit":50}`
80
+
81
+ ## Errors
82
+
83
+ - Missing required mode
84
+ - Missing or blank target for a target-based mode
85
+ - Option irrelevant to the selected mode (refused, never ignored)
86
+ - `limit` on a mode it does not apply to, or `limit: 0`
87
+ - A `complexity` target that names no indexed file and no directory prefix
88
+
89
+ ## Limitations
90
+
91
+ - Static relationships cannot establish runtime behavior
92
+ - Test results describe indexed static evidence, not executed coverage; a project whose tests the indexer did not recognise is reported as having none, with the reason
93
+ - Callers/callees/trace/impact modes share the graph engine's owner-cascade semantics: a relationship stored against a class member is attributed to the owner class. Every edge states which it was through `targetMatch` (`exact` or `owner-cascade`); absent means the engine could not tell, which is not the same as `exact`
94
+ - Callers/callees/trace answer from the graph engine's DEFAULT kind set, which is EVERY kind the index stores — so a `callers` set means "who has a stored edge INTO this", not only who calls it. `contains` is the parent→member edge, so a class that CONTAINS a symbol is reported as that member's caller (membership, not dependency); `uses`/`references` are lexical evidence and `imports`/`exports` are module-level. Measured on this engine's own answers: lodash `baseConvert` answered 99 `calls` rows of 230 edges, cobra `getCompletions` 36 of 65, rake `standard_rake_options` 72 of 76. Every edge carries its stored `relationshipType`, so the composition is readable per answer. Mode `impact` does NOT share that set: it walks the declared dependency kinds (`calls`, `instantiates`, `extends`, `implements` — `src/lsp/impact.ts` `IMPACT_REL_TYPES`) and states the rule it applied, so a `callers` count and an `impact` count about the same symbol answer different questions
95
+ - A name resolves to ONE stored symbol, and WHICH one is declared in `rootMatch`: `exact` (a structural symbol of that name), `exact-any-kind` (a symbol of that name the graph cannot traverse — a constant, a type alias — which still has callers), or `containment`. `containment` is the documented near-miss fallback and means the answer is about a symbol whose name CONTAINS the one asked for. Measured on this repository's src: `target: "us"` answered `found: true` rooted at `analyzeCSharpBareIdentifierUses`, and `target: "_"` answered `found: true` rooted at `build_registry` when no symbol is named `_`. `found: true` alone does not mean the name was a symbol
96
+ - A CASE difference resolves by containment, because the exact step is `name = ?` (case-sensitive) and SQLite's `LIKE` is case-insensitive for ASCII. So `target: "HUB"` and `target: "hub"` are the same answer, reached by the weaker step — which is why `rootMatch` says `containment` for the first
97
+ - `limit` has two ends and both are declared: refused below 1 (an empty page is indistinguishable from a real answer) and refused above 5,000 for the graph modes / 2,000 for the rest. It is refused rather than clamped, because a returned page must mean the page that was asked for. Measured on this repository's src, `limit: 2147483648` was accepted before this and answered with the whole corpus
98
+ - `complexity` accepts a directory prefix at ANY depth, including a top-level directory. It did not: the prefix branch required a `/` in the target, so `target: "ast"` was refused as "no indexed file matches" while 84 indexed files sat under it, and the same for every top-level directory on every project
99
+ - `complexity` publishes both file counts: `filesMatchingScope` is how many files the scope names and `filesInScope` is how many hold a symbol the metrics are over. They differ when a file in scope has no indexed symbol (83 of 84, measured), and `linesOfCode` covers the second number — so the gap is visible instead of an unexplained discrepancy
100
+ - `test_mapping.summary.productionSymbols` and `test_gaps.summary.productionSymbols` are the SAME number, and each report also states `excludedAsNoise`, so `productionSymbols + excludedAsNoise` is the store's population under the mode's own filter. `test_gaps.summary.total` is a count of GAPS, not of production symbols — it is the population minus `directlyTested`, and the identity closes in public
101
+ - A `not-found` answer is bounded: the target is echoed at most 200 characters with its full length stated. A 4,000-character target produced a 9,409-byte answer (~2.6k tokens for two words of information) before this. The bound is on the ECHO only — the search always uses the whole string
102
+ - A name resolves among several stored symbols. `rootCandidates` says how many — 1 means the name was unique, more means the answer is about one of several
103
+ - mode `impact` refuses a target the graph cannot root at, with `notFoundReason: "not-traversable"` and the symbol type. Measured on a fixture: a type alias and a namespace refuse; a constant and an enum DO root (`found: true`, `rootMatch: "exact-any-kind"`), so the kind alone does not predict the outcome — the field does. Such a symbol IS in the index; `callers`/`callees` resolve the same name because they match any type
104
+ - A depth-capped walk is TRUNCATION and says so: `summary.truncated` is true with `truncationReason` naming the depth cap, beside `maxDepth` and `maxDepthReached`
105
+ - `test_impact` refuses `limit`, so its declared bound is the same 2,000 ms walk ceiling the command accepts no page size for: `summary` carries `returned`, `total`, `truncated`, `truncationReason`, `timeBudgetMs`, `timeBudgetExhausted` and `depthCap`. An answer with `truncated: true` names the bound that fired
106
+ - `test_quality`'s `limit` bounds the files ANALYSED and the files RETURNED: `summary` carries `returned`, `totalFiles` and `truncated`, so `limit: 1` answers one row and says it is one of three, not three rows with no page statement
107
+ - `complexity`'s `breakdown` is a page of at most the 20 highest-complexity symbols; `breakdownReturned` and `breakdownTruncated` state what was cut, beside `symbolCount` (the population)
108
+ - A record's `stale` flag means its file changed after indexing OR is gone from disk: `missing` is not `fresh`, and the modes that carry row flags (callers/callees/trace/dependencies/complexity/impact/test_mapping/test_gaps/test_quality) all mark it
109
+ - `dependencies` and the three test modes accept an EXACT project-relative file as `filePath`, not only a directory prefix; matching is on a whole segment boundary, so `src/util` does not match `src/utilities`
110
+ - These three modes' walks run under a wall-clock ceiling THIS TOOL declares — 2,000 ms, published on the answer as `summary.timeBudgetMs` with `summary.timeBudgetExhausted` saying whether it fired (`walk-time-budget-exhausted` among the reasons). It is not an inherited engine default: the graph engine applies no clock bound unless a caller asks for one, so the ceiling is visible here rather than implicit. It is kept because `mode=trace` on a hub is the walk that motivated a clock bound at all. A node is not a unit of work: the per-node caller query is priced by the reproducible ORDER BY, which SQLite applies before the LIMIT, so `edgeLimit` cannot bound it. Measured on this repository's own src: 2,436 matching rows for one symbol cost 85.8 ms of the 87.8 ms, and the walk was unbounded until this budget existed
111
+ - `dependencies` resolves an internal specifier against the importing file's own directory and LABELS each edge `resolved` / `suffix-match` / `unresolved`. It is not a guess from a basename: on this repository's own src only 74 of 906 `imports` relationships carry both endpoints and `brain_import.resolved_symbol_id` is empty, so specifier resolution is the primary path, not a fallback
112
+ - A file's `dependents` and `dependencies` lists are capped per row; `dependentsTotal` / `dependenciesTotal` are the real sizes and the Truncated flags say the list was cut
113
+ - Neither list carries `fileId` — a file id is `<40-hex project id>:<relative path>`, so it repeated the path printed beside it (56,540 of 121,340 bytes on a default page of this repository's src). The path is the locator, `resolution` says how it was reached, and the row above carries the id for the file the row is about
114
+ - `metrics.couplingEdges` counts only the relationship types listed in `couplingRelationshipTypes`; `exports` and `contains` are index bookkeeping. `stats.relationships` is every row and is a different number
115
+ - `metrics.maintainability` is unavailable when the formula's domain does not cover the scope (it is negative above roughly 3·10⁴ non-blank lines) rather than clamped to 0
116
+ - Locations are project-relative. `nodes[].filePath` (absolute) is not emitted, so the same question returns the same bytes on any machine
117
+ - A mapping entry carries at most 20 links (`linksTruncated` says when more exist); the ranking and every summary count use the complete set
118
+ - `provenance` on a `dependencies` page is on the REPORT, not repeated per row
119
+
120
+ ## Performance
121
+
122
+ Every collection mode is a page and says so. `limit` bounds the answer — 25 edges per direction for callers/callees/trace (default), 20 files for dependencies, 50 entries for test_mapping/test_gaps, 50 files for test_quality. Measured warm (five runs, p50/p95, in validation/tools/bb_analyze/LATENCY.json): on a 5-file fixture every default mode answers in under 60 ms; on a 97-file synthetic index under 404 ms. On this workspace's reference index (6,452 files, 310,423 symbols, 820,040 relationships) stats answers in 5.2/7.4 s, complexity 2.8/2.9 s, test_quality 0.16/0.31 s, and the graph modes are bounded by a declared 2,000 ms walk ceiling that a single SQL statement can exceed — callers answered 11.0/12.4 s and callees 29.3/64.0 s on that index under fleet load. Default payloads on the reference index: stats 6.2 KB, complexity 5.4 KB, callers (hub) 33.6 KB, dependencies 203.8 KB, test_mapping 66.1 KB, test_quality 73.1 KB. A caller who wants less passes `limit` where the mode accepts it. The graph walk is additionally bounded by depth, per-node site limit, fan-out budget, and the wall-clock budget; whichever fires is named in `truncationReason`.
123
+
124
+ ## Safety
125
+
126
+ Read-only. Analysis never creates tests or edits source.
127
+
128
+ ## Evidence Semantics
129
+
130
+ Findings derive from brain_* symbols, files, relationships, complexity, and test-file classifications. `provenance.evidenceClass` classifies what kind of claim the answer is; `unknowns` names every absence the answer rests on.
131
+
132
+ ## Relationship to other tools
133
+
134
+ For the plain per-symbol call graph use `bb_graph` (same engine, evidence tiers). For bounded impact-with-files use mode `impact` (numbers identical to `bb_impact`).
135
+
136
+ ## Deep guides
137
+
138
+ - [Analysis modes and output shapes](../BB_ANALYZE.md)
139
+ - [Test intelligence (mode: tests)](../BB_TEST_INTEL.md)
140
+ - [Test quality (mode: test_quality)](../BB_TEST_QUALITY.md)
141
+ - [Recorded analysis runs](../BB_ANALYSIS_HISTORY.md)
142
+
143
+ ## Staleness
144
+
145
+ Freshness modes (registered as `freshnessModes`): `callers`, `callees`, `trace`, `dependencies`, `stats`, `complexity`, `impact`, `test_mapping`, `test_gaps`, `test_impact`, `test_quality`. Staleness surfaces as: `freshness` block (`{ fresh, stale, missing, staleRatio, mode, remediation }`); graph nodes and report rows carry `stale` flags (project-wide modes sample or resolve visible rows only — the block names its coverage mode). When something is stale/missing, the remediation names the registered reindex interfaces: project scope → `reads auto-refresh stale files within budget when enabled — disable with BB_NO_AUTO_REFRESH=1 or refresh=false; manual repair: run: bb index --changed (or the bb_index tool with the project path — incremental, changed files only)`; file scope → `reads auto-refresh this file within budget when enabled — disable with BB_NO_AUTO_REFRESH=1 or refresh=false; manual repair: run: bb index "<file>" (or the bb_index tool with that file path)`. Nothing is stale → silence (no remediation text). Detection uses a mtime+size fast path with a content-hash fallback: an edit that leaves both size and mtime unchanged is reported fresh without hashing (same trade-off as git's index stat-cache; accepted, documented in `src/brain/freshness.ts`).
146
+
147
+ This page is generated from the executable registry and curated documentation. Run `npm run docs:generate` to update it, or `npx tsx script/verify-tool-docs.ts --only <tool> --write` for a single page.
@@ -0,0 +1,110 @@
1
+ <!--
2
+ Author: Muhammad Ishaq Khan
3
+
4
+ WHAT THIS DOCUMENT IS — the generated reference for bb_annotate: contract, input schema, returns, examples, errors, limitations, performance, safety and evidence semantics.
5
+ WHO IT IS FOR — callers of bb_annotate through any MCP host, the CLI (`bb tool bb_annotate`), or a library.
6
+ EDIT TOGETHER WHEN THIS CHANGES — src/mcp/tools/bb_annotate/descriptor.ts, src/mcp/tools/bb_annotate/handler.ts, script/tool-docs/curation/bb_annotate.ts; regenerate with `npx tsx script/verify-tool-docs.ts --only bb_annotate --write`.
7
+ -->
8
+ # `bb_annotate`
9
+
10
+ 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 — 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 — 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.
11
+
12
+ ## Contract
13
+
14
+ - Category: `annotations`
15
+ - Mutability: `write`
16
+ - Requires index: `false`
17
+ - Modes: `annotate`
18
+ - Planned modes (roadmap, not executable): (none)
19
+ - Defaults: `{}`
20
+
21
+ ## Input Schema
22
+
23
+ ```json
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "symbol": {
28
+ "type": "string",
29
+ "description": "symbol name to annotate, max 500 chars (`target`/`query` are accepted as aliases); it is not required to exist in the index — reachability is measured and reported"
30
+ },
31
+ "body": {
32
+ "type": "string",
33
+ "description": "annotation text, max 2000 chars counted in code points (an astral glyph is one character); empty or whitespace-only is refused"
34
+ },
35
+ "path": {
36
+ "type": "string",
37
+ "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"
38
+ },
39
+ "toolIdentity": {
40
+ "type": "string",
41
+ "description": "optional name of the calling agent/tool, max 200 chars (recorded verbatim; line breaks and control characters are refused)"
42
+ }
43
+ },
44
+ "required": [
45
+ "symbol",
46
+ "body"
47
+ ]
48
+ }
49
+ ```
50
+
51
+ ## Returns
52
+
53
+ - The stored annotation row with its enforced provenance (`author_class` is always `agent` for tool writes) and the caller's self-declared `toolIdentity`, recorded verbatim with `time_created`/`time_updated`
54
+ - `replaced` — present only when this location already held a different note: the previous author class, tool identity, body and timestamps, so the second writer can merge instead of clobbering. The previous note does not survive in the store
55
+ - `surfacing` — a MEASUREMENT against the index, not a promise: `editor` (bool, the JOINT truth — the name or qualified name must be indexed AND declared in the resolved file), `nameMatched`, `pathMatched`, `fileMatched` (`true`/`false` when a path was given, `null` for a name-level note), `matchedAs` (`name` and/or `qualifiedName`), `matchedNames`, and a `statement` saying what is and is not true. A name that is indexed only in a DIFFERENT file than the one the path resolves to is reported `editor: false` with that reason — the hover and graph badge select notes by the file under the cursor, so those two individually-true halves would render nowhere
56
+ - The path handling: `surfacing.pathNote` when a path was resolved to the index's own spelling, `surfacing.pathWarning` when no indexed file matches the given path (stored under that spelling, no editor surface will scope it to a file), and a name-level note statement when no path was given
57
+
58
+ ## Examples
59
+
60
+ `{"symbol":"handleRequest","body":"Legacy auth path — scheduled for removal in Q3."}`
61
+
62
+ ## Errors
63
+
64
+ - Missing or blank symbol
65
+ - Symbol longer than the declared 500-char limit — refused, never truncated
66
+ - Empty or whitespace-only body (a refusal, not a silent no-op)
67
+ - Body longer than the declared 2000-char limit, counted in characters (an astral glyph is one character, not two UTF-16 units)
68
+ - Path longer than the declared 1024-char limit — refused, never truncated
69
+ - toolIdentity longer than the declared 200-char limit — refused, never truncated
70
+ - toolIdentity containing a control character or line break — a label is a name, not a document. Refused, because a line break in that field is exactly what once rendered as a SECOND provenance line that could claim to be a human note
71
+ - Unknown options (including `author_class`/`authorClass`) are rejected by name with the accepted option list — provenance is not a caller input
72
+
73
+ ## Limitations
74
+
75
+ - Annotations are opinions layered on index evidence, never index truth
76
+ - Any symbol name is accepted. Reachability is reported, not enforced: a name no indexed symbol carries is stored and declared `surfacing.editor: false`
77
+ - The note is keyed on the name as given. A name that matches several indexed symbols is one name-level note shown wherever that name appears, unless `path` scopes it to a file
78
+ - `surfacing.editor` is the joint rule the editors apply: the symbol must be declared in the file the path resolves to (or the note must be name-level). A name match plus a path match for a different file is reported `editor: false`
79
+ - `path` is resolved to the index's own spelling (`brain_file.relative_path`), so `src/a.ts`, `./src/a.ts` and the absolute path are the SAME note. A path matching no indexed file is stored verbatim and declared
80
+ - `toolIdentity` is the caller's own label. It is recorded verbatim and never verified — `author_class` is the enforced field
81
+ - The editor renderer escapes EVERY interpolated field, not only the body: `author_class`, `tool_identity` and `body` all go through the escaper, and a line break in any of them is flattened to a space, so one stored row always renders as one line. Markup escaping alone is not enough — a newline is what turns one annotation into two in a markdown list
82
+ - One note is kept per author per location, so a second agent annotating the same symbol+path REPLACES the first note. The replacement is reported in `replaced` with the previous body, but the previous note does not survive in the store. Annotating a DIFFERENT path (or no path) keeps a separate note per location
83
+ - On a project that has never been indexed, the first write creates that project's annotation store; the reply still reports `surfacing.editor: false` because no symbol can be matched yet
84
+ - This tool does not report per-file index staleness and does not auto-refresh: output reflects the index as of the last index run
85
+
86
+ ## Performance
87
+
88
+ Measured through the real MCP stdio door on isolated indexes, five warm runs per tier (validation/tools/bb_annotate/LATENCY.json): p50 83.4 / p95 96.1 ms on the 5-file polyglot fixture; p50 5.8 / p95 12.1 ms on the 97-file n100 fixture; p50 5.7 / p95 17.9 ms on the 564-file graphql-js corpus; p50 3.5 / p95 5.1 ms on the 109-file rake corpus; p50 3.8 / p95 5.8 ms on the 6,452-file / 310,423-symbol reference index. The first call in a fresh server process opens the project index read-write and runs its additive migrations, measured at 1.8 s on the reference-index snapshot. The write itself is two indexed name lookups (name, qualified name), one file-row lookup for the path and — when a path is given — one symbol-in-file lookup for the joint reachability answer, plus the location upsert.
89
+
90
+ ## Safety
91
+
92
+ Write-capable against the annotation store only. It never modifies source files. It opens the project index read-write the way every write door does, so the additive schema migrations may apply on the first call; index content outside brain_annotation is not written.
93
+
94
+ ## Evidence Semantics
95
+
96
+ Provenance is enforced three ways: the write hardcodes 'agent', the column is NOT NULL, and a storage trigger constrains `author_class` to {agent, human} — so a row cannot carry a third claim that every reader would render verbatim. `toolIdentity` and timestamps are recorded, and the toolIdentity claim is a label, not evidence. Reachability is measured against the same predicates the readers use: `brain_symbol.name`/`qualified_name` for the name, `brain_file.relative_path` for the file.
97
+
98
+ ## Relationship to other tools
99
+
100
+ To change source code use `bb_edit`; this tool stores provenance-enforced notes on symbols. Read them back with `bb_annotations`.
101
+
102
+ ## Deep guides
103
+
104
+ - [Writing annotations: provenance, bounds, surfacing](../BB_ANNOTATE.md)
105
+
106
+ ## Staleness
107
+
108
+ This tool does not report per-file index staleness and does not auto-refresh. Output reflects the index as of the last index run; when files changed after indexing, refresh first: run: bb index --changed (or the bb_index tool with the project path — incremental, changed files only).
109
+
110
+ This page is generated from the executable registry and curated documentation. Run `npm run docs:generate` to update it, or `npx tsx script/verify-tool-docs.ts --only <tool> --write` for a single page.
@@ -0,0 +1,106 @@
1
+ <!--
2
+ Author: Muhammad Ishaq Khan
3
+
4
+ WHAT THIS DOCUMENT IS — the generated reference for bb_annotations: contract, input schema, returns, examples, errors, limitations, performance, safety and evidence semantics.
5
+ WHO IT IS FOR — callers of bb_annotations through any MCP host, the CLI (`bb tool bb_annotations`), or a library.
6
+ EDIT TOGETHER WHEN THIS CHANGES — src/mcp/tools/bb_annotations/descriptor.ts, src/mcp/tools/bb_annotations/handler.ts, script/tool-docs/curation/bb_annotations.ts; regenerate with `npx tsx script/verify-tool-docs.ts --only bb_annotations --write`.
7
+ -->
8
+ # `bb_annotations`
9
+
10
+ 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 — 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 — the symbol's simple name AND its qualified name — 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.
11
+
12
+ ## Contract
13
+
14
+ - Category: `annotations`
15
+ - Mutability: `read`
16
+ - Requires index: `false`
17
+ - Modes: `annotate`
18
+ - Planned modes (roadmap, not executable): (none)
19
+ - Defaults: `{"limit":50}`
20
+
21
+ ## Input Schema
22
+
23
+ ```json
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "symbol": {
28
+ "type": "string",
29
+ "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"
30
+ },
31
+ "path": {
32
+ "type": "string",
33
+ "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 — 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"
34
+ },
35
+ "limit": {
36
+ "type": "number",
37
+ "description": "max rows returned (default 50, min 1, max 200); a truncated answer states the true total and a reason",
38
+ "default": 50,
39
+ "minimum": 1,
40
+ "maximum": 200
41
+ },
42
+ "includeNameLevel": {
43
+ "type": "boolean",
44
+ "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"
45
+ }
46
+ },
47
+ "required": []
48
+ }
49
+ ```
50
+
51
+ ## Returns
52
+
53
+ - Typed annotations for a symbol, a file, or the whole project, with full provenance on every row (author class, tool identity, timestamps)
54
+ - `count` (the page size), `totalCountIfKnown` (the true number of matching annotations) and `truncated` with a `truncationReason` naming an action the caller can take — at the declared cap the reason asks for a narrower filter instead of a limit raise it cannot honour
55
+ - `includeNameLevel` echoes the accepted flag and `nameLevelIncluded` states the filter actually applied (a symbol request always includes name-level notes about that symbol)
56
+ - `symbolIndexed` says whether the requested spelling named an indexed symbol; a spelling the index does not publish falls back to the literal stored name, so a note written under an arbitrary name is still findable
57
+ - `storeAbsent` is true when the project has no annotation store yet: the answer is a declared absence and the read created nothing
58
+ - Declared-absent result when none exist — absence is never an error
59
+
60
+ ## Examples
61
+
62
+ `{"symbol":"handleRequest","path":"src/server.ts"}`
63
+
64
+ `{"symbol":"Widget.render"}`
65
+
66
+ `{"includeNameLevel":true,"path":"src/index.ts"}`
67
+
68
+ ## Errors
69
+
70
+ - limit outside the declared 1-200 range (0, negative, above 200, fractional, or not a number) is refused with the rule named
71
+ - an option the tool does not accept is refused with the accepted option set named, and a conflicting alias/canonical pair is refused rather than silently resolved
72
+
73
+ ## Limitations
74
+
75
+ - Reads only stored annotations, and does not create, migrate or repair the store while reading; absence is declared, not inferred
76
+ - Without `path`, a symbol that lives in several files returns every file's notes together; pass `path` (or read each row's `symbol_path`) to separate them
77
+ - A `symbol` resolves to the simple name AND the qualified name of every symbol it names, the same pair the editor hover matches, so a note written under either form is found from either spelling. This is the editor's predicate, not a prose promise
78
+ - A `path` is resolved to the index's own spelling before filtering, so a reader passing `./src/a.ts` finds the note a writer stored as `src/a.ts`; a path the index does not know is used as given
79
+ - A `path` filter WITHOUT a `symbol` returns that file's own notes only and excludes the project's name-level notes by default; pass `includeNameLevel: true` for the broader set. With a `symbol` AND a `path`, name-level notes about THAT symbol are always included
80
+ - The registry declares one nominal action name for the store (`annotate`); the tool takes no `mode` option — every call is a read and a request carrying `mode` is refused as an unknown option
81
+
82
+ ## Performance
83
+
84
+ A symbol read and its declared total are indexed lookups by project and symbol name (`brain_annotation_location_idx`; the earlier `brain_annotation_project_symbol_idx` is superseded by it). The project-wide shapes seek by project and then sort the matched rows (`ORDER BY time_updated DESC, id`), and a path-only read scans the project's annotation rows because the store has no index on `symbol_path` — the cost is proportional to the number of notes in the project, not to the number of files. On a 206-note fixture the path scan measures 0.14 ms and the project-wide page 0.34 ms.
85
+
86
+ ## Safety
87
+
88
+ Read-only, measured: the store is opened byte-read-only, so a read does not create a database for an unindexed project, does not recreate a missing annotation table in a legacy store, and does not move the write-ahead log. It does not modify source files, index data, or annotations.
89
+
90
+ ## Evidence Semantics
91
+
92
+ Rows come from brain_annotation with provenance fields preserved.
93
+
94
+ ## Relationship to other tools
95
+
96
+ To write a note use `bb_annotate`; this tool reads stored annotations, declares absence, and opens the store without writing it.
97
+
98
+ ## Deep guides
99
+
100
+ - [Reading annotations: filters, bounds, absence](../BB_ANNOTATIONS.md)
101
+
102
+ ## Staleness
103
+
104
+ This tool does not report per-file index staleness and does not auto-refresh. Output reflects the index as of the last index run; when files changed after indexing, refresh first: run: bb index --changed (or the bb_index tool with the project path — incremental, changed files only).
105
+
106
+ This page is generated from the executable registry and curated documentation. Run `npm run docs:generate` to update it, or `npx tsx script/verify-tool-docs.ts --only <tool> --write` for a single page.
@@ -0,0 +1,101 @@
1
+ <!--
2
+ Author: Muhammad Ishaq Khan
3
+
4
+ WHAT THIS DOCUMENT IS — the generated reference for bb_ask: contract, input schema, returns, examples, errors, limitations, performance, safety and evidence semantics.
5
+ WHO IT IS FOR — callers of bb_ask through any MCP host, the CLI (`bb tool bb_ask`), or a library.
6
+ EDIT TOGETHER WHEN THIS CHANGES — src/mcp/tools/bb_ask/descriptor.ts, src/mcp/tools/bb_ask/handler.ts, script/tool-docs/curation/bb_ask.ts; regenerate with `npx tsx script/verify-tool-docs.ts --only bb_ask --write`.
7
+ -->
8
+ # `bb_ask`
9
+
10
+ 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) — 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 — matched by name, not by similarity, with its neighbours listed and labelled as neighbours; (3) SEMANTIC RETRIEVAL over stored embeddings — 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 — 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–0.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 — file changed since index]` or `[MISSING — 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.
11
+
12
+ ## Contract
13
+
14
+ - Category: `search`
15
+ - Mutability: `read`
16
+ - Requires index: `true`
17
+ - Modes: `answer`
18
+ - Planned modes (roadmap, not executable): `explain`, `feature`
19
+ - Defaults: `{"limit":5}`
20
+
21
+ ## Input Schema
22
+
23
+ ```json
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "query": {
28
+ "type": "string",
29
+ "description": "natural-language question, or a symbol name for the exact-name route"
30
+ },
31
+ "limit": {
32
+ "type": "number",
33
+ "minimum": 1,
34
+ "maximum": 100,
35
+ "description": "max results (default 5; whole number 1..100; values outside are refused, not clamped). A larger page also means a larger work budget."
36
+ }
37
+ },
38
+ "required": [
39
+ "query"
40
+ ]
41
+ }
42
+ ```
43
+
44
+ ## Returns
45
+
46
+ - Exact aggregates over the indexed snapshot — symbol counts by kind (functions count both `function`- and `method`-kind symbols), relationships, imports, files (optionally by language), the largest file and the most complex function — each answer carrying the index snapshot's timestamp
47
+ - An exact symbol-name lookup when the query is a symbol name, with its neighbours listed and explicitly labelled as neighbours rather than better matches
48
+ - Ranked semantic matches with real cosine similarities when stored embeddings allow it, each answer naming the compared model, the symbols comparable in that model space, the library-wide coverage, the scan bounds, and per-source `[STALE — file changed since index]` / `[MISSING — file no longer on disk]` markers
49
+ - Lexical matches when embeddings are unavailable or nothing clears the similarity floor — with NO similarity number printed, because none was computed, and the fallback reason named
50
+ - Declared bounds on every answer: `returned`, `totalMatches` (or an explicit unknown), `truncated` and `truncationReason`; plus a confidence kind: exact aggregate, exact name lookup, mean of returned similarities (bounded 0.2–0.85), declared floor, or none when the ranking is declared non-discriminative
51
+
52
+ ## Examples
53
+
54
+ `{"query":"how many functions are in this project"}`
55
+
56
+ `{"query":"where is request handling implemented"}`
57
+
58
+ `{"query":"handleRequest","limit":1}`
59
+
60
+ ## Errors
61
+
62
+ - Empty or whitespace-only query
63
+ - `limit` outside 1..100 (0 included) — refused rather than clamped, because a clamped page looks like the page the caller asked for
64
+ - Unknown option, or a mode that exists only in `plannedModes` (`explain`, `feature`)
65
+ - No index for the project, or an index that cannot be read — answered as a declared unknown with zero sources, never a fabricated count
66
+
67
+ ## Limitations
68
+
69
+ - Semantic similarity discovers candidates; it does not prove behaviour, ownership or architecture, and the answer says so
70
+ - Local word vectors are a 205-word programming vocabulary plus the project's own vocabulary hashed into 50 dimensions. Their cosines are not comparable with a model embedding's, and an identifier-shaped query whose tokens are all absent produces a zero vector, reported as `reason=degenerate-query-vector`
71
+ - Coverage is per comparison space. On a mixed-model index the symbols comparable in the query model's space can be far fewer than the library-wide number; both numbers are reported and labelled
72
+ - When the configured embedding provider does not answer within the tool's wait ceiling (2 s, or `BB_EMBEDDING_TIMEOUT_MS` when lower) the question is embedded with the local word-vector model, the answer declares the timeout, and the ranking is limited to that model's space
73
+ - A verified aggregate is exact about the indexed snapshot, not the working tree: files changed after the last index write are not reflected, and the answer reports the snapshot time so the reader can judge the gap
74
+ - The dense candidate scan is capped at `BB_VECTOR_SCAN_CAP` rows (default 20,000). When the cap is reached the answer declares `truncated=true`, and the candidates are a deterministic prefix of the embedding table ordered by entity id
75
+ - A non-discriminative ranking (several candidates within the 0.05 margin of the best) publishes no numeric confidence: the answer says the scores do not separate candidates instead of printing a number it calls unreliable
76
+
77
+ ## Performance
78
+
79
+ Warm default-argument latency on the 3.6 GB reference index (6,452 files / 310,423 symbols) is p50 ≈ 1.8 s and p95 ≈ 2.1 s with the configured embedding provider; the first call in a cold process takes 9–10 s because the scan loads up to 20,000 vectors (≈164 MB at 2048 dimensions). The exact symbol-name route with `limit: 1` skips the dense scan and answers in milliseconds. The page size bounds both the payload and the work budget, and the candidate scan cap bounds memory independently of index size.
80
+
81
+ ## Safety
82
+
83
+ Read-only. Every index open is byte-read-only: the tool does not create a database, run migrations, heal indexes or promote embeddings, and it never writes source or rebuilds embeddings behind the caller's back. When embeddings are configured, the natural-language question itself is sent to that embedding provider to compute the query vector; with local word vectors the whole call stays offline.
84
+
85
+ ## Evidence Semantics
86
+
87
+ Treat matched symbols and resolvable source locations as evidence; treat similarity scores as candidate ranking, not proof. A non-discriminative ranking is not evidence and is labelled as such. A verified aggregate is a fact about the indexed snapshot, with that snapshot's timestamp reported.
88
+
89
+ ## Relationship to other tools
90
+
91
+ For exact symbol/content/file retrieval use `bb_search`; this tool answers natural-language questions with citations.
92
+
93
+ ## Deep guides
94
+
95
+ - [Asking questions honestly — routes, confidence, bounds](../BB_ASK.md)
96
+
97
+ ## Staleness
98
+
99
+ Freshness modes (registered as `freshnessModes`): `answer`. Staleness surfaces as: per-source `[STALE — file changed since index]` markers on retrieval answers, repeated in the source list; a verified aggregate answer carries no per-file marker (it describes the whole index, not a file). This tool does not auto-refresh, and it emits no `freshness` envelope: a verified aggregate answer is a count over the whole index rather than a per-file fact, so there is no per-file envelope to publish. When a cited source is stale, remediation is manual: run: bb index --changed (or the bb_index tool with the project path — incremental, changed files only). Nothing is stale → no marker. Detection uses a mtime+size fast path with a content-hash fallback: an edit that leaves both size and mtime unchanged is reported fresh without hashing (same trade-off as git's index stat-cache; accepted, documented in `src/brain/freshness.ts`).
100
+
101
+ This page is generated from the executable registry and curated documentation. Run `npm run docs:generate` to update it, or `npx tsx script/verify-tool-docs.ts --only <tool> --write` for a single page.
@@ -0,0 +1,103 @@
1
+ <!--
2
+ Author: Muhammad Ishaq Khan
3
+
4
+ WHAT THIS DOCUMENT IS — the generated reference for bb_cross_language: contract, input schema, returns, examples, errors, limitations, performance, safety and evidence semantics.
5
+ WHO IT IS FOR — callers of bb_cross_language through any MCP host, the CLI (`bb tool bb_cross_language`), or a library.
6
+ EDIT TOGETHER WHEN THIS CHANGES — src/mcp/tools/bb_cross_language/descriptor.ts, src/mcp/tools/bb_cross_language/handler.ts, script/tool-docs/curation/bb_cross_language.ts; regenerate with `npx tsx script/verify-tool-docs.ts --only bb_cross_language --write`.
7
+ -->
8
+ # `bb_cross_language`
9
+
10
+ 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 — the copy of the requested name it came from and the other symbol's name, file, language and line — 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 → 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 — `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 — 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.
11
+
12
+ ## Contract
13
+
14
+ - Category: `graph`
15
+ - Mutability: `read`
16
+ - Requires index: `true`
17
+ - Modes: `cross-language`
18
+ - Planned modes (roadmap, not executable): (none)
19
+ - Defaults: `{"limit":200}`
20
+
21
+ ## Input Schema
22
+
23
+ ```json
24
+ {
25
+ "type": "object",
26
+ "properties": {
27
+ "symbol": {
28
+ "type": "string",
29
+ "description": "symbol name to fetch cross-language facts for (`target`/`symbolName`/`query` are accepted as aliases)"
30
+ },
31
+ "limit": {
32
+ "type": "number",
33
+ "description": "max facts to return (default 200, max 1000); truncation is declared, never silent. Refused below 1 or above 1000 rather than clamped",
34
+ "default": 200,
35
+ "minimum": 1,
36
+ "maximum": 1000
37
+ }
38
+ },
39
+ "required": [
40
+ "symbol"
41
+ ]
42
+ }
43
+ ```
44
+
45
+ ## Returns
46
+
47
+ - One fact per stored cross-language row about the name. `facts` holds the rows on the copies themselves; `memberFacts` holds the rows on their MEMBERS. Every fact names both ends — `self` (symbol, file, language, line) and `other` — plus the stored `rowId`, direction, bridge, evidence tier and confidence. **The fact-shape invariant:** `self` is the endpoint the STORED ROW NAMES (the copy for `match: "exact"`, the member for `match: "owner-cascade"`), so `pair` is always exactly `self.language → other.language` and any consumer can re-derive it. A member fact also carries `owner` — the copy of the asked name it was attributed through — and `matchedMember`/`matchedMemberId`, which are the member end and its stored id (a mirror of `self`, kept so either name reads the same)
48
+ - `copies`: every indexed symbol carrying the name, with file, language, line and cross-row count, plus `ambiguous` and `ambiguityNote`. `limit` governs FACTS; the copy list has its own cap — `copiesCap`, with `copiesTruncated` and a `copiesNote` when it bites. `copyCount` is always the TRUE total from its own query, never the length of a capped array, so `ambiguous` cannot be silenced by the cap
49
+ - Counts name what they count: `factCount` is the number of facts in this answer; `pairCount` is the number of DISTINCT language pairs in it (`"java → kotlin"` is one pair however many rows carry it); `signatureCount` is the number of distinct (pair, direction, bridge, tier) signatures; `totalCountIfKnown` is the number of facts that exist before the limit. `ownFactCount` and `memberFactCount` are the facts shown per section, and `ownFactsTruncated` / `memberFactsTruncated` say which section the budget cut. `memberCount` is the distinct members that contributed a member fact BEFORE the limit, so it can exceed `memberFactCount`
50
+ - Declared absence is a property of the STORE, not of the emitted facts: `declaredAbsent` is true only when neither the copies nor their direct members hold a stored cross-language row, and `absenceNote` says so — scoped to the project, because that is what the row queries searched. A row that exists but cannot name a language pair is NOT absence: it is counted in `unpairableRowCount`, the empty fact set is explained in `pairlessRowNote`, and `storedRowCount` still reports it
51
+ - `deeperDescendantRowCount` counts stored cross-language rows on descendants at depth 2 or deeper, BOTH directions, and does not cascade them into the facts (a descendant's edges are the descendant's). When the owner itself is silent but such rows exist, the absence note names the count instead of claiming the index holds nothing
52
+ - Declared truncation: `truncated`, `truncationReason` and `totalCountIfKnown` when the `limit` bounded the facts. `collapsedRowCount` counts a row matched from both directions and emitted once; a row that is counted there is never emitted twice
53
+
54
+ ## Examples
55
+
56
+ `{"symbol":"Warehouse"}`
57
+
58
+ `{"symbol":"Iterate"}`
59
+
60
+ `{"symbol":"invoke","limit":50}`
61
+
62
+ ## Errors
63
+
64
+ - Missing or blank symbol (refused, not defaulted)
65
+ - Symbol not found in the index — a declared error, not an empty set
66
+ - limit outside the declared 1..1000 range, a fractional limit, or a non-number limit — refused, never clamped silently
67
+
68
+ ## Limitations
69
+
70
+ - THE MEMBER CASCADE IS ONE LEVEL DEEP. A member's rows are reported as the member's, with the owner named; a grandchild's rows are counted in `deeperDescendantRowCount` and deliberately not emitted as the owner's or the member's. Ask about the descendant directly to see them
71
+ - Stored rows only. Nothing here infers an edge, a bridge mechanism or a tier. Every fact traces to a `brain_relationship` row with `is_cross_language = 1`, filtered by the ROW's own project id in this tool and in the editor's cross-language hover section, so a row minted for another project cannot appear as a fact about this one
72
+ - The absence note is scoped to ONE PROJECT: the same name indexed in another project may hold rows for it, and `absenceNote` says which store it searched rather than claiming anything about the whole index
73
+ - `bridge` is stored data, not an inference about communication. On indexed corpora almost every row carries the indexer's name-resolution placeholder, so a bridge value names how the row was minted, not a verified interop mechanism. A row with no stored mechanism renders a fixed `unnamed bridge` text rather than an invented one
74
+ - A name held by many symbols is answered as the UNION over all of them; every fact names the copy it came from, so an agent can narrow, but the tool never chooses one copy on the agent's behalf and does not resolve which copy a human means
75
+ - Rows that cannot name a pair are counted, not dropped: an endpoint whose file has no detected language, or two endpoint languages that are equal, produces no fact and increments `unpairableRowCount`. No shipped corpus reaches that state today, so it is verified by seeding the state rather than assumed — and a row that exists but cannot name a pair is reported as `pairlessRowNote`, never as absence
76
+ - `memberCount` describes the whole answer, while `memberFactCount` describes what the limit showed. When the two differ, `memberFactsTruncated` is true and `truncationReason` names the budget
77
+ - Tier classification is a stored-vs-derived contract: when the indexer stored `metadata.evidenceClass` the stored class is rendered; otherwise the derived rule in `src/lsp/evidence_tier.ts` is used. `confidence` is the stored row confidence and is rendered beside the tier, never instead of it
78
+
79
+ ## Performance
80
+
81
+ Measured with `validation/tools/bb_cross_language/work/latency.mts` (warm, five runs, p50/p95, machine load recorded) and `work/latency-t2-handler.mts` on a COPY of the 6,452-file reference index (6452 files, 310,423 symbols, 820,040 relationships). Tiny fixture (5 files, 5 stored cross-language rows): p50 8.3 ms / p95 8.6 ms. Synthetic 97-file single-language repository (the absence path): p50 38.9 ms / p95 59.7 ms. Reference index, handler against the copy, default arguments: `Command` p50 7.6 ms / p95 7.8 ms; `file` (3,457 copies) p50 112 ms / p95 169 ms; `type` (5,392 copies) p50 278 ms / p95 328 ms — 15x inside the 5 s ceiling. The two by-name statements and the depth>=2 count are index-driven on purpose: written naively they scanned the project's 820,040 relationship rows (measured 3.8–17.6 s per statement, p50 3.3–6.5 s per call, one sample at 33.6 s). Forcing the walking order to start at the symbol-name index and at the recursive descendant table — with every predicate preserved, proved row-set identical on a 692-file corpus with 714 stored rows — took one name from 14,641 ms to 26 ms and the depth count from 1.4–1.7 s to 5–36 ms. Both the pre-fix and post-fix rounds stay in `LATENCY.json` as the regression baseline.
82
+
83
+ ## Safety
84
+
85
+ Read-only. It does not modify source files or index data.
86
+
87
+ ## Evidence Semantics
88
+
89
+ Only `brain_relationship` rows with `is_cross_language = 1`, filtered by the ROW's own project id — in both this tool and the editor cross-language hover section — and by nothing else: no graph walk, no lexical id-prefix cascade, no inferred pair. Every fact carries the stored row id and both ends, so a reader can re-check it against the store without trusting the tool. Tiers are delegated to the single classifier in `src/lsp/evidence_tier.ts` over the declared derivation, so the same stored row cannot render two different tiers on two surfaces. Member rows follow the store's `owner_symbol_id` ownership edge, while `bb_graph`'s cascade is a lexical id-prefix range, so the two counts differ by design; the answer states which rule it used rather than reconciling the numbers. Cross-checked on a 692-file, 8-language corpus with 714 stored cross-language rows (403 checks, 16 planted defects all caught), against seeded store states no corpus reaches (43 seeded checks, 8 planted defects including a ROW-LEVEL project-predicate leak, each caught by a probe proved false on the unmutated code), and against the editor's hover projection (21 checks, 5 planted defects; both surfaces read the same owner module). See validation/m63/results/CL1.json, validation/tools/bb_cross_language/results/GATE.json and validation/m26/results/W3.json.
90
+
91
+ ## Relationship to other tools
92
+
93
+ For same-language callers/callees use `bb_graph`; this tool reports stored cross-language pairs and bridges (declared absent when none).
94
+
95
+ ## Deep guides
96
+
97
+ - [Cross-language facts: what is stored and what is not](../BB_CROSS_LANGUAGE.md)
98
+
99
+ ## Staleness
100
+
101
+ This tool does not report per-file index staleness and does not auto-refresh. Output reflects the index as of the last index run; when files changed after indexing, refresh first: run: bb index --changed (or the bb_index tool with the project path — incremental, changed files only).
102
+
103
+ This page is generated from the executable registry and curated documentation. Run `npm run docs:generate` to update it, or `npx tsx script/verify-tool-docs.ts --only <tool> --write` for a single page.