@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,323 @@
1
+ # Indexing performance — what to expect
2
+
3
+ > Every number on this page is quoted from a recorded evidence file in
4
+ > `validation/`, with the host it was measured on. Almost all of them come from
5
+ > **one** machine: an Intel Core i7-8650U Linux workstation with ~15.5 GiB RAM.
6
+ > They are that machine's numbers. They are **not** SLOs, not capacity claims,
7
+ > and not portable to your hardware. Where no measurement exists, this document
8
+ > says so instead of estimating.
9
+ >
10
+ > Statements about *behaviour* (which phase runs, which files are skipped) are
11
+ > gated against the code by `script/verify-docs-indexing.ts`; statements about
12
+ > *timings* are only as good as the evidence file named beside them.
13
+
14
+ ## The short version
15
+
16
+ - **A small project takes seconds. A large one takes minutes to hours. That is
17
+ normal.**
18
+ - **The cost is the number of eligible files**, and secondarily how expensive
19
+ each language's analyzer is — not the number of languages.
20
+ - **The parse half of a re-run is nearly free**, because unchanged files are
21
+ never re-parsed — on *every* mode, bare `bb index` included. On an unchanged
22
+ project a bare re-run parses nothing and reports `derived: skipped-unchanged`.
23
+ When a re-run *does* parse something, it pays for the full derived pass, where
24
+ `--changed` pays for a bounded one. That is what the flag buys.
25
+ - **The two dominant costs are parsing and writing to the database.** Discovery
26
+ and worker startup are measurable but negligible.
27
+ - If you want one number for your own project, run `bb index` once and read
28
+ `bb status`. There is no way to predict it from file count alone.
29
+
30
+ ## What actually determines the time
31
+
32
+ ### 1. How many files are eligible, not how many exist
33
+
34
+ Discovery skips `.gitignore`d paths, `.bbignore`d paths, built-in skip patterns
35
+ (`node_modules`, `dist`, `build`, `out`, `target`, `coverage`, …), and files
36
+ over 5 MB (`src/ast/parallel-indexer.ts:253-300`, `:850-877`).
37
+
38
+ Two corpora can both contain 250,000 files and differ by two orders of
39
+ magnitude in index time, because only the eligible ones are parsed. Discovery
40
+ itself is cheap — see the bottleneck table below — so **reducing what
41
+ discovery finds is the single most effective speedup you have.**
42
+
43
+ ### 2. How expensive each language's analyzer is
44
+
45
+ This is the dominant variable once file count is fixed. Measured full-index
46
+ wall times, one host:
47
+
48
+ | Corpus | Language | Files | Symbols | Wall | Evidence |
49
+ | --- | --- | ---: | ---: | ---: | --- |
50
+ | cargo | Rust | 1,328 | 16,088 | 38.6s | `validation/m11/systems-known-limitations.md:32` |
51
+ | cabal | Haskell | 2,140 | 19,137 | 141.0s | `validation/m11/fp-known-limitations.md:40` |
52
+ | elixir | Elixir + Erlang | 616 | 11,189 | 192.4s | `validation/m11/fp-known-limitations.md:38` |
53
+ | zig | Zig | 2,950 | 164,506 | 211s | `validation/m11/systems-known-limitations.md:33` |
54
+ | julia | Julia | 941 | 27,560 | 168–208s | `validation/m12/corpus-manifest.json` (the julia entry: 941 `*.jl` files, `corpusSymbols` 27,560) + `docs/analyzers/julia.md:2147` (the timing) |
55
+ | grails | Groovy | 7,114 | 64,719 | 494.6s | `reviews/groovy/master-review-2026-09-29.md:436-441` |
56
+
57
+ Note the shape: `cabal` has more files than `elixir` but takes **less** time;
58
+ `elixir` has fewer files than `cargo` but takes **five times** as long. File
59
+ count alone does not predict anything.
60
+
61
+ **There is no measurement in this repository of index time versus *number of
62
+ distinct languages*.** Every measurement above holds language count effectively
63
+ constant within a corpus. If you have a 20-language monorepo, nobody has
64
+ published a number for that shape and this document will not invent one.
65
+
66
+ ### 3. Whether the run has anything to do
67
+
68
+ Every run — incremental or not — parses only files whose `mtime` **and** `size`
69
+ differ from what is stored (`src/ast/parallel-indexer.ts:887-894`). On an
70
+ unchanged project that is zero files. The one recorded unchanged-project run —
71
+ rake, 57 files — completed in **85 ms** with `parsedFiles: 0` and `noop: true`
72
+ (`validation/mA/results/probe-index-ux.json`).
73
+
74
+ Verified live by `script/verify-docs-indexing.ts --live`: a second bare
75
+ `bb index` on an unchanged fixture parses nothing and reports
76
+ `derived: skipped-unchanged`.
77
+
78
+ ### 4. The derived phases
79
+
80
+ After parsing, the derived phases run over the whole project. Current code
81
+ records **eight**: `relationships`, `enrichment`, `gravity`, `taint`, `health`,
82
+ `integrity`, `vectorBlobs`, `analyze`
83
+ (`src/ast/parallel-indexer.ts:576`, `:5561-5680`). The measurement below predates
84
+ the last three being recorded, so its table has six rows — the five derived-data
85
+ phases plus `integrity`:
86
+
87
+ | Phase | Time |
88
+ | --- | ---: |
89
+ | relationships | 2,299ms |
90
+ | enrichment | 3,091ms |
91
+ | gravity | 409ms |
92
+ | taint | 2,582ms |
93
+ | health | 124ms |
94
+ | integrity | 905ms |
95
+ | **total post-processing** | **9,494ms** |
96
+
97
+ `validation/mA/results/probe-index-ux.json` (element-plus, 2,580 files,
98
+ 27,781 symbols, 47,480 relationships, 7 workers on an 8-CPU host).
99
+
100
+ So on that run: parse 141s, dbFlush 169s, post-processing 9.5s. **Parse and DB
101
+ flush dominate; enrichment is secondary.** Note this run's shape differs from a
102
+ cold full index — see the wall-clock caveat at the end of this document before
103
+ using it to reason about first runs.
104
+
105
+ **A phase's time can be a plan rather than work, and on a fresh index the plan is
106
+ chosen blind.** The pipeline runs `ANALYZE` at the END of the derived pass, so a
107
+ fresh index prepares these statements against tables nothing has measured. One
108
+ cold run reported 440.8s for the taint phase; the same statement, same data and
109
+ same 1,530 resolved flows cost **520.2s planned without statistics and 1.7s with
110
+ them** — and a post pass on the same index after the fix reported the phase at
111
+ 1.3s. Resolution, enrichment and gravity query tables the run has just written
112
+ too; only the taint statement has been measured and fixed so far
113
+ (`validation/m60/results/taint-plan-stats.json`, probe:
114
+ `script/perf/probe-taint-plan-stats.ts`). Before attributing a slow phase to its
115
+ algorithm, check whether its plan was chosen blind.
116
+
117
+ ## Where the time goes
118
+
119
+ Measured bottleneck classification across five repositories, taking the median
120
+ fraction of total wall time each phase consumed
121
+ (`validation/m21/results/X3.json`):
122
+
123
+ | Phase | Median fraction of wall | Verdict |
124
+ | --- | ---: | --- |
125
+ | Parse | **0.74** | confirmed bottleneck |
126
+ | DB flush | **0.345** | confirmed bottleneck |
127
+ | Worker startup (fork probe) | 0.009 | disproven as a bottleneck |
128
+ | Discovery | 0.002 | disproven as a bottleneck |
129
+
130
+ Practical consequences:
131
+
132
+ - **More CPU cores will not rescue a slow index past the worker cap.** Worker
133
+ count is `max(2, min(cpus - 1, 12))` (`src/ast/parallel-indexer.ts:120`), and
134
+ the pool then sizes itself as `min(concurrency, ceil(files / 10))`
135
+ (`src/ast/parallel-indexer.ts:1243`, `:131`). On a 32-core machine you still
136
+ get 12 workers. On a 4-core machine you get 3.
137
+ - **12 workers is not automatically faster than 1.** The one controlled A/B in
138
+ the repo (`validation/m19/results/S3-controls.json:1-60`, 100-file disjoint samples on
139
+ intellij) found 12 workers *faster* on the throughput control (20,705ms vs
140
+ 22,767ms) but *slower* on the memory control (39,775ms vs 25,482ms). The
141
+ evidence file states these were disjoint sequential samples, "not a
142
+ same-input benchmark". Treat "add workers" as unproven.
143
+
144
+ ## Does throughput decay as the index grows?
145
+
146
+ There is a recorded curve now. It says the per-file rate does not fall because
147
+ later files are more expensive — but the number a run finishes with *is* lower
148
+ than the number the bar showed at its best, for a different reason.
149
+
150
+ `validation/m60/results/index-scaling-curve.json` holds nested pool tiers
151
+ (n50 ⊂ n200 ⊂ n400 ⊂ n800 — prefixes of one deterministic ordering of this
152
+ repository's own TypeScript, materialised by `script/perf/make-pools.sh`, each
153
+ indexed into a fresh data dir by `script/perf/profile-stages.ts`), two real
154
+ corpora indexed through the CLI, and the rendered `files/s` series of two runs.
155
+ It carries two arms — the resolution plan before and after the linear fix — and
156
+ the raw per-tier output is committed beside it.
157
+
158
+ Pool tiers, after-arm, on the Linux workstation (load1 5.1–13.9 across the runs;
159
+ each tier is a superset of the tier above it):
160
+
161
+ | Tier | Files | Relationships | Wall | files/s | parse | dbFlush | derived |
162
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
163
+ | n50 | 47 | 7,968 | 19.8s | 2.4 | 84% | 21% | 7% |
164
+ | n200 | 196 | 63,331 | 76.1s | 2.6 | 80% | 47% | 17% |
165
+ | n400 | 396 | 91,379 | 105.8s | 3.7 | 74% | 54% | 23% |
166
+ | n800 | 796 | 124,114 | 147.0s | 5.4 | 72% | 70% | 26% |
167
+
168
+ Three things to read out of it:
169
+
170
+ - **files/s rises with size here, it does not fall** — 2.4 → 5.4 over 17× the
171
+ files, and the largest tier ran against *more* machine load than the smallest.
172
+ The small tiers are where discovery, worker startup and the first flush are a
173
+ larger fraction of a short run.
174
+ - **Parse and DB flush are the run at these sizes**, and workers overlap them, so
175
+ the two columns are not a partition and do not add up to 100%.
176
+ - **The derived phases grow as a share of the run** (7% → 26% across these tiers,
177
+ 41% at the 12k-file corpus below). What does *not* grow is the per-relationship
178
+ cost of resolution: 0.45 candidate rows per stored relationship at 47 files →
179
+ 0.57 at 196 files, a 1.25× growth over 4× the files, and resolve milliseconds
180
+ per relationship at 1.03×. Both growth factors are gated
181
+ (`validation/m60/results/scaling-linear.after-linux.json`).
182
+
183
+ The two arms of that curve produce the same *graph* at every tier: the
184
+ relationship-multiset digest is byte-identical between the window plan and the
185
+ linear plan at n50, n200, n400 and n800. What changed is cost, not output.
186
+
187
+ ### The rate the bar shows is not a parse rate
188
+
189
+ The `X/s` on the progress line — and in `bb status` while a run is in flight — is
190
+ `processed files / elapsed-since-start`: a running average over the **whole**
191
+ run. It climbs while the pool is saturating, peaks when the last file is stored,
192
+ and then decays through the derived pass, where no file completes and the clock
193
+ keeps running. `script/perf/live-rate-series.ts` captures it. On element-plus
194
+ (2,581 files) the recorded series peaks at **30.1 files/s at 85s**, and the run
195
+ as a whole reports 102.6s — **25.2 files/s**. That 20% gap is the tail, not a
196
+ parse slowdown. On lodash (73 files, 27.4s) the same arithmetic is starker: peak
197
+ 5.7 files/s, whole run 2.7 files/s.
198
+
199
+ So a figure like "22 files/s" is a whole-run number: files divided by everything
200
+ the run did, tail included. The parse-only rate is always higher, and the gap is
201
+ as large as the derived pass is.
202
+
203
+ ## Measured anchors, full index, from scratch
204
+
205
+ | Corpus | Files | Symbols | Wall | Evidence |
206
+ | --- | ---: | ---: | ---: | --- |
207
+ | rake | 57 | 779 | 1.93s | `validation/m19/results/S1.superseded-e7c9819f4.json:61-65` |
208
+ | lodash | 36 | 1,048 | 3.41s | `validation/m19/results/S1.superseded-e7c9819f4.json:68-72` |
209
+ | element-plus | 2,580 | 27,751 | 51.58s | `validation/m19/results/S1.superseded-e7c9819f4.json:75-79` |
210
+ | paperclip | 676 | 8,054 | 100.9s | `validation/m19/results/S2-paperclip-timings.json:33` |
211
+
212
+ **Do not read these as a scaling curve.** They are three points on three
213
+ different codebases and the relationship is non-monotonic: lodash (36 files)
214
+ took *longer* than rake (57 files). The evidence file says so itself
215
+ (`validation/m19/results/S1.superseded-e7c9819f4.json:104`): *"Wall-time is
216
+ hardware-bound (machine recorded); not portable"* and, for the second quote,
217
+ `docs/m19/BENCHMARKS.md:14-16`.
218
+
219
+ The full per-phase breakdown for one complete run, element-plus:
220
+
221
+ ```
222
+ total 151805ms | parse 141079ms | dbFlush 169079ms | discovery 646ms | startup 322ms
223
+ mode worker-pool | workers 7 | noop no
224
+ 18.1 files/s
225
+ ```
226
+
227
+ `validation/mA/results/probe-index-ux.json`. (The `dbFlush` figure exceeds the
228
+ parse figure because flushing is overlapped with parsing across workers; the two
229
+ are not additive.)
230
+
231
+ ## Very large repositories
232
+
233
+ | Corpus | Indexed files | Symbols | Wall | Status |
234
+ | --- | ---: | ---: | ---: | --- |
235
+ | ASP.NET Core | 10,431 | 132,744 | 161s + 92s + 77s + 68s, four slices | verified, finalization deferred per slice (`validation/m19/results/S2-timings.json:55,109,163,217`) |
236
+ | IntelliJ Community | 69,509 | 1,071,964 | **not claimed** | counts verified; a single fresh 1,000-file batch took 1,216.9s, so a complete cold run was outside the verification window (`validation/m19/results/S3.superseded-e7c9819f4.json:116-131`, `validation/m19/results/B1.json:42-43`) |
237
+ | groovy (grails) | 7,114 | 64,719 | 494.6s | single process, all tracked files, 0 failures |
238
+ | ASP.NET Core | 12,177 | 196,030 | 2,207.7s | one cold run at this repository's HEAD, full derived pass: 898.8s of the 2,207.7s (41%) is post-processing — `taint` alone is 440.8s, larger than the 297.2s resolution phase (`validation/m60/results/index-scaling-curve.json`) |
239
+
240
+ The last row is a different measurement from the four-slice row above it: a
241
+ single cold run, on a later build, with the derived pass included rather than
242
+ deferred per slice. Its shape is the one worth carrying away — **41% of a 37-minute
243
+ index stores no files at all**, so a `files/s` figure for a corpus this size is
244
+ dominated by work that has nothing to do with files.
245
+
246
+ The IntelliJ row is the important one. A complete cold full-index wall time for
247
+ a repo of that size has **not been measured** and this repository does not
248
+ claim one. Anyone who tells you "it'll take N hours" for a 70k-file Java repo is
249
+ extrapolating, not reporting.
250
+
251
+ ## How to speed it up
252
+
253
+ In descending order of effect:
254
+
255
+ 1. **Use `bb index --changed`, not a bare `bb index`.** Both parse only files
256
+ whose mtime or size changed — that filter is not a flag. What `--changed`
257
+ changes is the derived pass: `bounded` (scoped to touched files) instead of
258
+ `full` (the whole project). On a steady codebase this is close to free.
259
+ 2. **Cut discovery with `.bbignore`.** Discovery is cheap in absolute terms
260
+ (0.002 of wall) but it decides how many files the expensive phases must
261
+ handle. Excluding generated code, vendored trees and fixtures with a
262
+ `.bbignore` (same syntax as `.gitignore`, and it overrides `.gitignore` —
263
+ `src/ast/parallel-indexer.ts:889-894`, `:969-971`) is the highest-leverage
264
+ change you can make on a huge repository. Use it to exclude what you have
265
+ excluded, not to exclude source you intend to search.
266
+ 3. **Split parse from derived work.** `bb index --skip-post-processing` parses
267
+ and stores and stops; `bb index --post` derives later. The bounded derived
268
+ pass is genuinely cheaper than the full one — one recorded comparison was
269
+ 74ms bounded vs 1,940ms full on the same project
270
+ (`validation/m40/results/A2.json:10-24`) — though that measurement is small
271
+ and project-specific.
272
+ 4. **Fix a silent serial run.** If the summary says `parsed in-process — the
273
+ worker pool was unavailable`, you are paying for a single-threaded parse and
274
+ every number else looks fine. See
275
+ [`troubleshooting.md`](troubleshooting.md); this is the most common cause of
276
+ "indexing is mysteriously slow".
277
+ 5. **Target one file when you only changed one file.** `bb index --file <path>`
278
+ parses one file and scopes the derived pass to it.
279
+ 6. **Do not reach for `--force` to make things faster.** It cannot; it is the
280
+ most expensive mode, because it re-parses everything and recomputes every
281
+ derived phase.
282
+
283
+ ## Honest limits of this document
284
+
285
+ - **Every timing is one machine's.** Intel i7-8650U, ~15.5 GiB RAM, Linux
286
+ (`validation/m19/results/S1.json:15-20`). Hardware, disk and load change these
287
+ numbers by large factors. Load alone moved this repo's own measured latency
288
+ from p50 1,655ms to p50 2,618ms at roughly double the machine load
289
+ (`validation/m57/results/T3-latency-idle.json:53-58`).
290
+ - **No cold full-index time exists for this repository.** Its index has been
291
+ measured for *counts* (2,681 files / ~168k symbols;
292
+ `docs/m57/issues/t1.md:13`) and for *read-tool latency*, but no `bb index`
293
+ wall clock is recorded anywhere. Don't infer one from the symbol count.
294
+ - **Declared budgets are not measurements.** `docs/M10-BENCHMARKS.md:52-53`
295
+ declares `index cold <= 8s` and `warm single-file reindex <= 1.5s` for a
296
+ ~12-file contract fixture — but `docs/data/m10-benchmark-last-run.json`
297
+ records **no** `index_cold` or `index_warm_reindex` row. Those budgets have
298
+ never been measured. Treat them as unverified.
299
+ - **No throughput figure is claimed for the three M19 anchors.**
300
+ `validation/m19/results/S1.json` records milliseconds only. The 18.1
301
+ files-per-second for element-plus comes from a different, second run of that
302
+ corpus on a different host (`validation/mA/results/probe-index-ux.json`); the
303
+ newer figures on this page — 23.9 and 25.2 files/s for element-plus, 5.5 for
304
+ ASP.NET Core — come from `validation/m60/results/index-scaling-curve.json` and
305
+ are one machine's and two builds', not a portable rate.
306
+ - **Per-language analyzer timings are not comparable across languages.**
307
+ `validation/m60/analyzer-budget-register.json:24-30` records that 39 of 41
308
+ lanes carry an unresolved scoring debt and that no complexity artifact in the
309
+ repo records node counts, so per-1k-node normalization **cannot be derived**.
310
+ The analyzer table above is a list of measured wall times, not a league table.
311
+ - **Embedding time as a share of index time is unknown.**
312
+ `docs/m20/PERFORMANCE-DIAGNOSTIC.md:24` explicitly declines to attribute a
313
+ slow run to embeddings or to WASM parsing for lack of evidence. Embeddings are
314
+ drained in the background after the parse, so they usually land outside the
315
+ measured index window — which is exactly why nobody can attribute them.
316
+ - **Nothing here is a promise about your machine.** The only reliable number for
317
+ your project is the one your own `bb status` prints after your own run.
318
+
319
+ ## Related
320
+
321
+ - [`README.md`](README.md) — what each flag actually does
322
+ - [`resume.md`](resume.md) — interrupting and resuming
323
+ - [`troubleshooting.md`](troubleshooting.md) — when the run degrades to serial
@@ -0,0 +1,345 @@
1
+ # Interrupting and resuming an index
2
+
3
+ > Gated by `script/verify-docs-indexing.ts` (see [`README.md`](README.md)). Source
4
+ > of truth: the cooperative-cancellation path in `src/cli/index.ts:259-271`, the
5
+ > live status file in `src/ast/index-status.ts`, the mtime/size skip in
6
+ > `src/ast/parallel-indexer.ts:887-894`, and the cancel terminal state at
7
+ > `src/ast/parallel-indexer.ts:5313-5324`.
8
+
9
+ An interrupted `bb index` does not have to start over. This document covers how
10
+ to interrupt one safely, what survives, how to resume, how to check that the
11
+ resume actually worked, and what to do when it cannot.
12
+
13
+ ## How to interrupt safely
14
+
15
+ Press **Ctrl-C**.
16
+
17
+ That is not a raw kill. The CLI installs a `SIGINT` handler and aborts
18
+ cooperatively (`src/cli/index.ts:259-271`):
19
+
20
+ ```
21
+ SIGINT received — cancelling index (draining in-flight files; resume with: bb index --resume)...
22
+ ```
23
+
24
+ - New batches stop being dispatched immediately.
25
+ - Batches already in flight are allowed to finish.
26
+ - The database therefore keeps only **fully stored** files. A file that was
27
+ half-written is not half-indexed — it is simply not in the index yet. This is
28
+ what makes the run resumable at all: the store, not a worklist, is the
29
+ checkpoint (`src/ast/index-status.ts:19-32` states the contract).
30
+ - Heavy post-processing is skipped entirely on a cancelled run. A user who pressed Ctrl-C wants a
31
+ fast stop, and every derived phase rewrites rows the checkpoint assumes are
32
+ untouched.
33
+ - When the process exits, you get:
34
+
35
+ ```
36
+ ⏸ index cancelled — progress saved. Resume with: bb index --resume
37
+ ```
38
+
39
+ `SIGTERM` is handled identically (`src/cli/index.ts:269`), so `kill <pid>` is
40
+ also safe. What is **not** safe is `kill -9` or a power cut: the process dies
41
+ with no chance to write the terminal status. The stored data is still
42
+ consistent — batches are committed, not held in memory — but the run never
43
+ labels itself, which changes what `--resume` has to go on. See
44
+ [When resume cannot find a checkpoint](#when-resume-cannot-find-a-checkpoint).
45
+
46
+ **Do not `kill -9` an index run.** If you think it is hung, check
47
+ [`troubleshooting.md`](troubleshooting.md) first; a serial in-process run on a
48
+ large project legitimately looks idle for minutes.
49
+
50
+ ## What the checkpoint is
51
+
52
+ Two things, and it matters which one you are looking at.
53
+
54
+ **1. The database is the real checkpoint.** Resume is not a snapshot and there
55
+ is no undo log. A resume simply re-runs the incremental filter: for every
56
+ discovered file, if the stored record's `mtime` and `size` both match the file
57
+ on disk, the file is skipped (`src/ast/parallel-indexer.ts:887-894`).
58
+
59
+ That filter is not a resume feature — it runs on **every** mode, bare `bb index`
60
+ included. Resume is cheap because of the store, not because resume re-plays a
61
+ worklist.
62
+
63
+ This is why resume works, and also why it is honest about its limits:
64
+
65
+ - Files already stored are skipped — **no re-parse, no time cost**.
66
+ - Files queued but never parsed are picked up.
67
+ - Files whose mtime *and* size changed are re-parsed, which is correct.
68
+ - A file edited to the same byte size with a preserved mtime is skipped. This is
69
+ the one real blind spot in the scheme, and it is inherent to mtime+size
70
+ comparison, not to resume specifically.
71
+
72
+ **2. The status file is the progress marker.** A small JSON file per project at
73
+ `<data-dir>/status/<projectId>.json` (`src/ast/index-status.ts:177`; the data
74
+ dir is `~/.bb` or `$BB_DATA_DIR`, `src/util/bb-data-dir.ts:11-13`). Shape, per
75
+ the `LiveIndexStatus` interface (`src/ast/index-status.ts:107+`) — the values
76
+ below are illustrative:
77
+
78
+ ```json
79
+ {
80
+ "schemaVersion": 2,
81
+ "projectId": "1d3d57103be91b8f9716d70199fe28a3f39245d9",
82
+ "phase": "cancelled",
83
+ "processedFiles": 70,
84
+ "totalFiles": 2348,
85
+ "filesPerSecond": 3.9,
86
+ "currentFile": "src/ast/engine.ts",
87
+ "startedAt": 1759428000000,
88
+ "updatedAt": 1759428019000
89
+ }
90
+ ```
91
+
92
+ `phase` is the field that matters, and its value set is a closed list —
93
+ `indexing`, `post-processing`, `complete`, `failed`, `cancelled`
94
+ (`src/ast/index-status.ts:84`). There is no `idle`: a project that was never
95
+ indexed has **no status file at all**, which is how "no run yet" is represented.
96
+
97
+ **When it is written:** on every progress tick (throttled to ~100 ms,
98
+ `src/ast/parallel-indexer.ts:142` and the tick site) and once at the end of the
99
+ run with the terminal phase (`src/ast/parallel-indexer.ts:5313-5324`). A
100
+ cancelled run keeps `phase: "cancelled"` and is **never** relabelled `complete`
101
+ — the `if (!stats.cancelled)` guard at `src/ast/parallel-indexer.ts:5329` is what
102
+ guarantees it. That phase is what lets a later resume tell "the user stopped
103
+ this on purpose" apart from "the process died".
104
+
105
+ Writes are temp-file + `rename`, so a reader sees either the whole previous
106
+ checkpoint or the whole new one, never a splice
107
+ (`src/ast/index-status.ts:34-41`).
108
+
109
+ You can read it without the CLI:
110
+
111
+ ```
112
+ cat ~/.bb/status/<projectId>.json
113
+ ```
114
+
115
+ The project id is the first 8 characters `bb index` prints in
116
+ `indexing <dir> (project 1d3d5710…)`. `bb status` shows the same data, parsed.
117
+
118
+ ## How to resume
119
+
120
+ ```
121
+ bb index --resume
122
+ ```
123
+
124
+ The command reads the checkpoint and tells you which branch it took
125
+ (`src/cli/index.ts:216-229`):
126
+
127
+ ```
128
+ resuming interrupted index (checkpoint: 70/2348 files done)
129
+ ```
130
+
131
+ or, when there is nothing to resume — reproduced by running it:
132
+
133
+ ```
134
+ no in-progress checkpoint; running incremental (only missing/changed files)
135
+ ```
136
+
137
+ **That second line is not an error.** Resume unconditionally runs incrementally
138
+ (`src/cli/index.ts:228`), so with no checkpoint it degrades to exactly the same
139
+ work as `bb index --changed` and exits normally. You never have to guess whether
140
+ `--resume` will work — it always runs, and it always tells you which mode it
141
+ used.
142
+
143
+ **Be careful what `--resume` reports on an up-to-date project.** With nothing
144
+ left to parse it takes the same no-op path a bare re-run does, and reports
145
+ `derived: skipped-unchanged`, not `bounded`. Measured:
146
+
147
+ ```
148
+ no in-progress checkpoint; running incremental (only missing/changed files)
149
+ ✓ nothing to index — already up to date (7 unchanged, 0 skipped, 0.3s)
150
+ breakdown: 0 newly parsed | 7 unchanged | 0 skipped | 0 symbol-loss (parsed 0 symbols) | 0 errors | 0 timeouts
151
+ parsed nothing — no files needed parsing, so no parse mode was used
152
+ derived: skipped-unchanged (no files parsed, so no derived pass ran)
153
+ consistency: every internal relationship is attached to a symbol (external references are attached to an external identity, which is correct)
154
+ ```
155
+
156
+ When it *does* have files to parse, `bounded` is what it reports:
157
+
158
+ ```
159
+ derived: bounded (derived data re-processed for touched files (bounded pass))
160
+ bounded: 11 symbols touched
161
+ ```
162
+
163
+ A real cancelled-then-resumed pair, from
164
+ `validation/m21/results/R3-cancel-resume.json`:
165
+
166
+ | Line | Value |
167
+ | --- | --- |
168
+ | Original run, live status | `{"phase":"cancelled","processedFiles":70,"totalFiles":2348}` |
169
+ | Original run, breakdown | `70 newly parsed \| 0 unchanged \| 0 skipped \| 70 symbol-loss (parsed 0 symbols) \| 0 errors \| 0 timeouts \| 2278 cancelled` |
170
+ | Resume message | `resuming interrupted index (checkpoint: 70/2348 files done)` |
171
+ | Resume progress | `[progress] 2340/2348 files (99%) 118.2/s ETA 0s` |
172
+
173
+ The `[progress]` prefix in that recorded run is from an older renderer. The
174
+ current one draws a fixed-width bar on a TTY and prefixes pipe output with
175
+ `[index]` (`src/cli/index-progress.ts:202`, `:206-239`); do not pattern-match on
176
+ that prefix.
177
+
178
+ ## Verify the resume was correct
179
+
180
+ A resume can finish cleanly and still leave derived data stale. This is not a
181
+ hypothetical — it is a measured case, and the counts on screen look healthy
182
+ while the edges are unattached. Check three lines, in this order.
183
+
184
+ ### 1. The `breakdown:` line
185
+
186
+ ```
187
+ breakdown: 2278 newly parsed | 0 unchanged | 0 skipped | 0 symbol-loss (parsed 0 symbols) | 0 errors | 0 timeouts
188
+ ```
189
+
190
+ `newly parsed` should account for the files the checkpoint said were outstanding.
191
+ `cancelled` should be gone from the line — a cancelled bucket on a resume means
192
+ the resume itself was interrupted. Note the `symbol-loss` bucket carries a
193
+ parenthetical in the real line; a doc that omits it is quoting from memory.
194
+
195
+ ### 2. The `derived:` line
196
+
197
+ ```
198
+ derived: bounded (derived data re-processed for touched files (bounded pass))
199
+ ```
200
+
201
+ `bounded` is what you want. The other values mean something specific:
202
+
203
+ | Value | Meaning |
204
+ | --- | --- |
205
+ | `full` | all derived data recomputed |
206
+ | `bounded` | recomputed for what this run touched |
207
+ | `skipped-unchanged` | no files parsed, so no derived pass ran |
208
+ | `deferred` | derived data is stale — run `bb index --post` |
209
+ | `partial` | a derived phase failed; index marked partial, exit 1 |
210
+
211
+ `skipped-unchanged` on a resume is the trap. A resume that finishes the parse
212
+ with nothing left to parse runs no derived pass — and the previous run was
213
+ cancelled *during* relationship resolution, so it left edges unresolved. The
214
+ relationship **count** recovers to the clean value while a set of edges is
215
+ still unattached, and every count on screen reads healthy. This exact sequence is
216
+ described in the code at `src/cli/index.ts:389-405`.
217
+
218
+ ### 3. The `consistency:` line
219
+
220
+ ```
221
+ consistency: every internal relationship is attached to a symbol (external references are attached to an external identity, which is correct)
222
+ ```
223
+
224
+ Note the wording: the all-clear talks about **internal** relationships.
225
+ External references legitimately have no `target_symbol_id` — that is how an
226
+ edge to something outside the project is represented — so counting them as
227
+ failures would report thousands of phantom problems on a healthy large index.
228
+
229
+ This is a real measurement taken from the store after the run, not an inference
230
+ from the pass label (`src/cli/index.ts:406-449`). If it is non-zero, you have
231
+ the trap above. The code emits this shape
232
+ (`src/cli/index.ts:440-442`; the example count is illustrative, not a
233
+ measurement):
234
+
235
+ ```
236
+ consistency: 16 internal relationship(s) are attached to neither a symbol nor an external reference — they are counted above but read as edges by no traversal. Run: bb index --post
237
+ ```
238
+
239
+ There is a third form worth knowing:
240
+
241
+ ```
242
+ consistency: not measured for this run (pre-dates the unresolved-relationship count)
243
+ ```
244
+
245
+ That is **not** an all-clear. "No line" and "checked, nothing found" look
246
+ identical on a terminal, so the code names the check as not run
247
+ (`src/cli/index.ts:443-448`). If you see it, run `bb index --post` to get a real
248
+ measurement.
249
+
250
+ Fix it with:
251
+
252
+ ```
253
+ bb index --post
254
+ ```
255
+
256
+ It parses nothing, so it is cheap, and it exits 1 if any phase fails.
257
+
258
+ A fourth check, if you want the whole picture: `bb stats` for committed counts,
259
+ and `bb status` for the previous run's per-phase timings and worker count.
260
+
261
+ ## When resume cannot find a checkpoint
262
+
263
+ `--resume` recognizes a checkpoint only when **both** conditions hold
264
+ (`src/cli/index.ts:219-222`):
265
+
266
+ 1. the status file's `phase` is `indexing` **or** `cancelled`, and
267
+ 2. `updatedAt` is less than **one hour** old.
268
+
269
+ So a checkpoint is *not* found when:
270
+
271
+ | Situation | Why | What to do |
272
+ | --- | --- | --- |
273
+ | The run finished successfully | `phase` is `complete` | Nothing. Your index is whole. |
274
+ | The run errored out | `phase` is `failed` | Read the error. `bb status` shows it. |
275
+ | `kill -9`, power loss, OOM | No terminal phase was ever written, and the last tick may already be >1h old | `bb index --resume` still runs, as `--changed` — the database skip means stored files are not re-parsed regardless. If files were still queued, they get parsed. |
276
+ | More than an hour between the crash and the resume | Freshness window expired | Same as above. The window governs *whether the checkpoint is reported*, not whether resume works. |
277
+ | `BB_DATA_DIR` changed, or you are a different user | The status file is somewhere else | Same as above. The database, not the status file, is what makes resume cheap. |
278
+ | The index lock is held | Another run is active | Wait for it. See the lock message in [`README.md`](README.md#one-index-run-at-a-time). |
279
+
280
+ The important thing: **a missing checkpoint degrades resume, it does not break
281
+ it.** The worst case is that you pay for an incremental pass plus a bounded
282
+ derived pass. On an unchanged project the parse half is zero files — a recorded
283
+ 57-file project re-indexed in 85 ms with `noop: true`
284
+ (`validation/mA/results/probe-index-ux.json`) — but the bounded derived pass
285
+ still runs, so "close to free" is about the parse, not the whole run.
286
+
287
+ ## Recovering with `--force`, honestly
288
+
289
+ `bb index --force` is the rebuild-everything escape hatch. Before reaching for
290
+ it, know what it costs.
291
+
292
+ **It is the most expensive mode `bb index` has.** It wipes the project's data
293
+ in place and then re-parses every eligible file and recomputes every derived
294
+ phase from zero. On the measured anchors:
295
+
296
+ | Corpus | Files | Symbols | Full index wall time | Evidence |
297
+ | --- | ---: | ---: | ---: | --- |
298
+ | rake | 57 | 779 | 1.9s | `validation/m19/results/S1.superseded-e7c9819f4.json:61-65` |
299
+ | lodash | 36 | 1,048 | 3.4s | `validation/m19/results/S1.superseded-e7c9819f4.json:68-72` |
300
+ | element-plus | 2,580 | 27,751 | 51.6s | `validation/m19/results/S1.superseded-e7c9819f4.json:75-79` |
301
+ | paperclip | 676 | 8,054 | 100.9s | `validation/m19/results/S2-paperclip-timings.json:33` |
302
+ | grails (Groovy) | 7,114 | 64,719 | 494.6s | `reviews/groovy/master-review-2026-09-29.md:436-441` |
303
+
304
+ All of these were measured on one Intel i7-8650U / 15.5 GiB Linux host. They are
305
+ **that machine's** numbers, not yours and not a promise.
306
+
307
+ > The S1 evidence file was superseded when a later run found the corpora absent on
308
+ > its machine: `validation/m19/results/S1.json` now records empty anchors. The
309
+ > measurement above is preserved verbatim in
310
+ > `validation/m19/results/S1.superseded-e7c9819f4.json`, which is what the table
311
+ > cites. See
312
+ [`performance.md`](performance.md).
313
+
314
+ There is also a real functional cost: the wipe is in place, so a concurrent
315
+ reader sees an empty database until the rebuild finishes
316
+ (`src/cli/index.ts:181-184`). Anyone querying the index during a `--force` gets
317
+ partial or empty results.
318
+
319
+ **Use `--force` when:**
320
+
321
+ - `bb doctor` reports `model-mismatch` or `model-mixed` — the fix it names is
322
+ `bb index --force`, because vectors from a different model can never be
323
+ compared against your queries (`src/setup/doctor.ts:159-181`).
324
+ - The index is genuinely corrupt: counts that contradict the file tree,
325
+ `database is locked` that survives stopping every writer, or a schema you no
326
+ longer trust.
327
+
328
+ **Do not use `--force` for:**
329
+
330
+ - A cancelled run. That is `--resume`.
331
+ - Stale derived data. That is `--post`, which parses nothing.
332
+ - A few changed files. That is `--changed` or `--file`.
333
+
334
+ ## A recovery order that works
335
+
336
+ ```
337
+ bb status # what phase is the index actually in?
338
+ bb index --resume # cheap; degrades to --changed safely
339
+ # then read the run's own report:
340
+ # derived: → must not be deferred
341
+ # consistency: → must be 0 unresolved
342
+ bb index --post # cheap; parses nothing; fixes derived data
343
+ bb doctor # read-only; flags model mismatch + permissions
344
+ bb index --force # last resort; full rebuild
345
+ ```