@backbencher/brain 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +298 -0
- package/EMBEDDINGS.md +355 -0
- package/LICENSE +21 -0
- package/README.md +415 -2
- package/bin/bb +13 -0
- package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
- package/dist/brain-index-health-KY7K552E.js +17 -0
- package/dist/brain-relationship-enrichment-QH7OOHUF.js +558 -0
- package/dist/brain.sql-EIM4WS4D.js +29 -0
- package/dist/chunk-2BSKD7O3.js +40 -0
- package/dist/chunk-2DE27DVC.js +1314 -0
- package/dist/chunk-2RRBMNBQ.js +322 -0
- package/dist/chunk-2RTN2DFU.js +711 -0
- package/dist/chunk-2WXYNLBA.js +29 -0
- package/dist/chunk-3CTFIJJB.js +17 -0
- package/dist/chunk-3LFTSMIV.js +174 -0
- package/dist/chunk-47HKX4AJ.js +930 -0
- package/dist/chunk-4H22TLOT.js +543 -0
- package/dist/chunk-5SD45FLC.js +818 -0
- package/dist/chunk-66A5ZFYA.js +1758 -0
- package/dist/chunk-6AIRWAO2.js +247 -0
- package/dist/chunk-6WNGD3NN.js +196 -0
- package/dist/chunk-7AN7U5IZ.js +371 -0
- package/dist/chunk-BHB7BYYI.js +626 -0
- package/dist/chunk-BQ3OVQ3P.js +125 -0
- package/dist/chunk-CW4XXSNN.js +1479 -0
- package/dist/chunk-D2Q4Y47A.js +53 -0
- package/dist/chunk-DIYAGQJL.js +34 -0
- package/dist/chunk-DYM4FQEE.js +90 -0
- package/dist/chunk-EWMWK7WN.js +75 -0
- package/dist/chunk-FVVQTSIM.js +3427 -0
- package/dist/chunk-FWSL4VG6.js +1290 -0
- package/dist/chunk-GBI7V7KY.js +69 -0
- package/dist/chunk-GEFFO6O2.js +152 -0
- package/dist/chunk-HOWAQXJO.js +236 -0
- package/dist/chunk-HQSPJDHI.js +613 -0
- package/dist/chunk-IFZNR5UJ.js +2863 -0
- package/dist/chunk-J4PMBXQQ.js +336 -0
- package/dist/chunk-KAET47ZL.js +125 -0
- package/dist/chunk-KCDECRJY.js +440 -0
- package/dist/chunk-KLK3PBTO.js +1119 -0
- package/dist/chunk-L5TDYKYK.js +76 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-OWISBAOF.js +61 -0
- package/dist/chunk-PJTNGD3Z.js +6924 -0
- package/dist/chunk-QLK6ED4Q.js +235 -0
- package/dist/chunk-R2GW4F6R.js +19 -0
- package/dist/chunk-RCMTFSEF.js +87 -0
- package/dist/chunk-RH7KC5HU.js +12050 -0
- package/dist/chunk-RM3PY6TG.js +376 -0
- package/dist/chunk-ROMM6KMC.js +433 -0
- package/dist/chunk-SK6YVHFZ.js +211 -0
- package/dist/chunk-SM7RFQRO.js +214 -0
- package/dist/chunk-UFE77L67.js +782 -0
- package/dist/chunk-UGR4QIGS.js +67 -0
- package/dist/chunk-UUNHHX4M.js +13 -0
- package/dist/chunk-V44PGPA6.js +2104 -0
- package/dist/chunk-VL52PL2T.js +71 -0
- package/dist/chunk-VURYFIHW.js +201 -0
- package/dist/chunk-XJKJYIPP.js +7436 -0
- package/dist/chunk-YI6FF2ZR.js +340 -0
- package/dist/cli/index.js +37 -0
- package/dist/config-HZAAYDP3.js +10 -0
- package/dist/content-search-642F7E5V.js +593 -0
- package/dist/database-TCTZDLE5.js +9 -0
- package/dist/doctor-GXCKSJOX.js +395 -0
- package/dist/edit-engine-KM7MPFEC.js +51 -0
- package/dist/engine-42TZFNIZ.js +11 -0
- package/dist/engine-LO6L4JLQ.js +18 -0
- package/dist/fingerprint-XDXFRWO6.js +33 -0
- package/dist/framework-intelligence-DWFCKV44.js +860 -0
- package/dist/freshness-3K6VU5OT.js +174 -0
- package/dist/graph-engine-PL5YA7BA.js +21 -0
- package/dist/graph_request-MM4REP6H.js +350 -0
- package/dist/impact_request-WDE47IZ5.js +68 -0
- package/dist/index-space-IUUN5WYV.js +15 -0
- package/dist/index-status-6BD7UBHR.js +34 -0
- package/dist/index.js +159 -0
- package/dist/instance-FO6QZRLH.js +19 -0
- package/dist/language-detection-DFCAU5DZ.js +11 -0
- package/dist/lsp/server.js +4301 -0
- package/dist/mcp/server.js +132 -0
- package/dist/onboard-DIQZV2NW.js +161 -0
- package/dist/override-companion-pass-XR7MHTP7.js +117 -0
- package/dist/parallel-indexer-4PW3AFXP.js +88 -0
- package/dist/pool-worker.js +1153 -0
- package/dist/project-health-ZS5TS2Z6.js +94 -0
- package/dist/project-index-5SDPHITE.js +57 -0
- package/dist/project-registry-DGZBIZS7.js +355 -0
- package/dist/real-bash-analyzer-SXSV67E2.js +2201 -0
- package/dist/real-c-analyzer-SY7FTZ6W.js +5942 -0
- package/dist/real-clean-analyzer-IGDHHLZK.js +3470 -0
- package/dist/real-clojure-analyzer-4OH7IWLB.js +2818 -0
- package/dist/real-cpp-analyzer-7LTDRJRJ.js +4101 -0
- package/dist/real-csharp-analyzer-KICU3TLH.js +3298 -0
- package/dist/real-css-analyzer-HPU277Q6.js +15 -0
- package/dist/real-dart-analyzer-7LZ5LCMW.js +3944 -0
- package/dist/real-elixir-analyzer-BE3MEVZO.js +2192 -0
- package/dist/real-erlang-analyzer-BZMAKYBK.js +1731 -0
- package/dist/real-fsharp-analyzer-5QLTOV3E.js +2390 -0
- package/dist/real-go-analyzer-POD7MZQR.js +4126 -0
- package/dist/real-graphql-analyzer-MAQRZ6QW.js +1442 -0
- package/dist/real-groovy-analyzer-MEWSUPMJ.js +2285 -0
- package/dist/real-haskell-analyzer-I5L2GR3Y.js +2533 -0
- package/dist/real-hcl-analyzer-DBOPXSWJ.js +725 -0
- package/dist/real-html-analyzer-O63XZLZD.js +1759 -0
- package/dist/real-java-analyzer-CHCW3EKA.js +4677 -0
- package/dist/real-javascript-analyzer-QAAJQYZE.js +14 -0
- package/dist/real-json-analyzer-UGBR7YQN.js +1640 -0
- package/dist/real-julia-analyzer-35Q4GHOZ.js +2085 -0
- package/dist/real-kotlin-analyzer-GRAYCWW2.js +3956 -0
- package/dist/real-lua-analyzer-5BAPDZDF.js +1957 -0
- package/dist/real-makefile-analyzer-YJKAHNAS.js +920 -0
- package/dist/real-markdown-analyzer-UKUQZO4F.js +1215 -0
- package/dist/real-matlab-analyzer-FVIKQZKG.js +480 -0
- package/dist/real-nim-analyzer-M7FAIABG.js +3603 -0
- package/dist/real-objective-c-analyzer-XMYQIGQI.js +1518 -0
- package/dist/real-perl-analyzer-ZLY6M2DZ.js +1865 -0
- package/dist/real-php-analyzer-KI2ZAM7X.js +9368 -0
- package/dist/real-powershell-analyzer-B3T5ZP6U.js +2915 -0
- package/dist/real-protobuf-analyzer-23OZ75XV.js +867 -0
- package/dist/real-python-analyzer-I4TIUB6D.js +4880 -0
- package/dist/real-r-analyzer-FJZJXXS6.js +2596 -0
- package/dist/real-ruby-analyzer-M3UFIVTQ.js +2429 -0
- package/dist/real-rust-analyzer-LVSONPAN.js +3359 -0
- package/dist/real-scala-analyzer-CWPI33Q2.js +3293 -0
- package/dist/real-scss-analyzer-TGRJQ7TH.js +1515 -0
- package/dist/real-solidity-analyzer-4P2VBVWA.js +2608 -0
- package/dist/real-sql-analyzer-J7DYZOZR.js +3840 -0
- package/dist/real-svelte-analyzer-NHEN2SX5.js +2532 -0
- package/dist/real-swift-analyzer-2NSYULWI.js +2311 -0
- package/dist/real-toml-analyzer-L3C66L7Q.js +929 -0
- package/dist/real-typescript-analyzer-FTC6ETRQ.js +14 -0
- package/dist/real-vb-analyzer-ORY7LAQS.js +3145 -0
- package/dist/real-vba-analyzer-JH2TTUFI.js +466 -0
- package/dist/real-vue-analyzer-OO4FUGBV.js +2680 -0
- package/dist/real-xml-analyzer-6CQVR477.js +2405 -0
- package/dist/real-yaml-analyzer-J6MQG3YX.js +3528 -0
- package/dist/real-zig-analyzer-EFKCPJNU.js +2430 -0
- package/dist/regex-search-OPTLKUN5.js +239 -0
- package/dist/rules-I453JTRC.js +152 -0
- package/dist/scoped-path-MJYWJZQX.js +11 -0
- package/dist/setup-show-Y5GB5KXA.js +15 -0
- package/dist/setup-wizard-G5YYR5T6.js +24 -0
- package/dist/symbol-search-KB4MU4SI.js +16 -0
- package/dist/taint-YOTN5X55.js +202 -0
- package/dist/taint-analyzer-N7YLZU3N.js +163 -0
- package/dist/tool-handlers-G4NAGJIH.js +72 -0
- package/dist/tool-registry-BPEJNSZG.js +17 -0
- package/dist/tool-validation-RF675A3C.js +32 -0
- package/dist/type-inference-4Y7WTMFE.js +13 -0
- package/dist/vector-binary-LMY4QGAN.js +19 -0
- package/dist/version-LXGNFO37.js +9 -0
- package/dist/wasm-NMWO3WXY.js +16 -0
- package/docs/EMBEDDINGS.md +132 -0
- package/docs/README.md +178 -0
- package/docs/data/m10-baseline.json +602 -0
- package/docs/data/m10-benchmark-baseline.json +57 -0
- package/docs/data/m10-benchmark-last-run.json +127 -0
- package/docs/data/m10-final-audit.json +215 -0
- package/docs/data/m12-final-audit.json +138 -0
- package/docs/data/m12-surface-matrix.json +50 -0
- package/docs/data/public-tools.json +1902 -0
- package/docs/indexing/DEAD-ASSETS.md +88 -0
- package/docs/indexing/INDEXER-REVIEW.md +634 -0
- package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
- package/docs/indexing/README.md +427 -0
- package/docs/indexing/discovery-matrix.md +41 -0
- package/docs/indexing/embeddings.md +325 -0
- package/docs/indexing/environment-variables.md +77 -0
- package/docs/indexing/performance.md +323 -0
- package/docs/indexing/resume.md +345 -0
- package/docs/indexing/troubleshooting.md +391 -0
- package/docs/man/bb.1 +190 -0
- package/docs/tools/bb_analyze.md +147 -0
- package/docs/tools/bb_annotate.md +110 -0
- package/docs/tools/bb_annotations.md +106 -0
- package/docs/tools/bb_ask.md +101 -0
- package/docs/tools/bb_cross_language.md +103 -0
- package/docs/tools/bb_doc.md +169 -0
- package/docs/tools/bb_edit.md +185 -0
- package/docs/tools/bb_graph.md +169 -0
- package/docs/tools/bb_health.md +156 -0
- package/docs/tools/bb_impact.md +117 -0
- package/docs/tools/bb_index.md +108 -0
- package/docs/tools/bb_migrate.md +135 -0
- package/docs/tools/bb_patterns.md +140 -0
- package/docs/tools/bb_search.md +150 -0
- package/docs/tools/bb_security.md +100 -0
- package/docs/tools/bb_visualize.md +184 -0
- package/grammars/README.md +57 -0
- package/grammars/THIRD-PARTY-NOTICES.md +50 -0
- package/grammars/tree-sitter-c/LICENSE +21 -0
- package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
- package/grammars/tree-sitter-cpp/LICENSE +21 -0
- package/grammars/tree-sitter-csharp/LICENSE +21 -0
- package/grammars/tree-sitter-css/LICENSE +21 -0
- package/grammars/tree-sitter-css/README.md +21 -0
- package/grammars/tree-sitter-dart/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/README.md +18 -0
- package/grammars/tree-sitter-elixir/LICENSE +176 -0
- package/grammars/tree-sitter-elixir/README.md +11 -0
- package/grammars/tree-sitter-fsharp/LICENSE +21 -0
- package/grammars/tree-sitter-fsharp/README.md +45 -0
- package/grammars/tree-sitter-graphql/LICENSE +21 -0
- package/grammars/tree-sitter-graphql/README.md +106 -0
- package/grammars/tree-sitter-groovy/LICENSE +21 -0
- package/grammars/tree-sitter-groovy/README.md +4 -0
- package/grammars/tree-sitter-haskell/LICENSE +21 -0
- package/grammars/tree-sitter-haskell/README.md +394 -0
- package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
- package/grammars/tree-sitter-hcl/LICENSE +201 -0
- package/grammars/tree-sitter-hcl/README.md +39 -0
- package/grammars/tree-sitter-html/LICENSE +21 -0
- package/grammars/tree-sitter-html/README.md +21 -0
- package/grammars/tree-sitter-julia/LICENSE +21 -0
- package/grammars/tree-sitter-julia/README.md +24 -0
- package/grammars/tree-sitter-kotlin/LICENSE +9 -0
- package/grammars/tree-sitter-lua/LICENSE.md +21 -0
- package/grammars/tree-sitter-lua/README.md +29 -0
- package/grammars/tree-sitter-make/LICENSE +21 -0
- package/grammars/tree-sitter-matlab/LICENSE +19 -0
- package/grammars/tree-sitter-matlab/README.md +99 -0
- package/grammars/tree-sitter-nim/LICENSE +373 -0
- package/grammars/tree-sitter-nim/README.md +46 -0
- package/grammars/tree-sitter-objc/LICENSE +21 -0
- package/grammars/tree-sitter-objc/README.md +20 -0
- package/grammars/tree-sitter-perl/LICENSE +7 -0
- package/grammars/tree-sitter-perl/README.md +100 -0
- package/grammars/tree-sitter-powershell/LICENSE +21 -0
- package/grammars/tree-sitter-powershell/README.md +7 -0
- package/grammars/tree-sitter-proto/LICENSE +21 -0
- package/grammars/tree-sitter-proto/README.md +105 -0
- package/grammars/tree-sitter-r/LICENSE +9 -0
- package/grammars/tree-sitter-r/README.md +66 -0
- package/grammars/tree-sitter-ruby/LICENSE +22 -0
- package/grammars/tree-sitter-scss/LICENSE +21 -0
- package/grammars/tree-sitter-scss/README.md +17 -0
- package/grammars/tree-sitter-solidity/LICENSE +7 -0
- package/grammars/tree-sitter-sql/LICENSE +21 -0
- package/grammars/tree-sitter-typescript/LICENSE +21 -0
- package/grammars/tree-sitter-vba/LICENSE +21 -0
- package/grammars/tree-sitter-vba/README.md +738 -0
- package/grammars/tree-sitter-vbnet/LICENSE +21 -0
- package/grammars/tree-sitter-vbnet/README.md +322 -0
- package/grammars/tree-sitter-xml/LICENSE +19 -0
- package/grammars/tree-sitter-yaml/LICENSE +20 -0
- package/grammars/tree-sitter-yaml/README.md +21 -0
- package/package.json +184 -4
- package/wasm/dart.wasm +0 -0
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-clean.wasm +0 -0
- package/wasm/tree-sitter-clojure.wasm +0 -0
- package/wasm/tree-sitter-core_schema.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-css.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-elisp.wasm +0 -0
- package/wasm/tree-sitter-elixir.wasm +0 -0
- package/wasm/tree-sitter-elm.wasm +0 -0
- package/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/wasm/tree-sitter-erlang.wasm +0 -0
- package/wasm/tree-sitter-fsharp.wasm +0 -0
- package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-graphql.wasm +0 -0
- package/wasm/tree-sitter-groovy.wasm +0 -0
- package/wasm/tree-sitter-haskell.wasm +0 -0
- package/wasm/tree-sitter-hcl.wasm +0 -0
- package/wasm/tree-sitter-html.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-json.wasm +0 -0
- package/wasm/tree-sitter-json_schema.wasm +0 -0
- package/wasm/tree-sitter-julia.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-lua.wasm +0 -0
- package/wasm/tree-sitter-make.wasm +0 -0
- package/wasm/tree-sitter-markdown.wasm +0 -0
- package/wasm/tree-sitter-markdown_inline.wasm +0 -0
- package/wasm/tree-sitter-matlab.wasm +0 -0
- package/wasm/tree-sitter-nim.wasm +0 -0
- package/wasm/tree-sitter-objc.wasm +0 -0
- package/wasm/tree-sitter-ocaml.wasm +0 -0
- package/wasm/tree-sitter-perl.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-php_only.wasm +0 -0
- package/wasm/tree-sitter-powershell.wasm +0 -0
- package/wasm/tree-sitter-protobuf.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ql.wasm +0 -0
- package/wasm/tree-sitter-r.wasm +0 -0
- package/wasm/tree-sitter-rescript.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-scss.wasm +0 -0
- package/wasm/tree-sitter-solidity.wasm +0 -0
- package/wasm/tree-sitter-sql.wasm +0 -0
- package/wasm/tree-sitter-svelte.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/wasm/tree-sitter-terraform.wasm +0 -0
- package/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/wasm/tree-sitter-toml.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter-vba.wasm +0 -0
- package/wasm/tree-sitter-vbnet.wasm +0 -0
- package/wasm/tree-sitter-vue.wasm +0 -0
- package/wasm/tree-sitter-xml.wasm +0 -0
- package/wasm/tree-sitter-yaml.wasm +0 -0
- package/wasm/tree-sitter-zig.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
- package/wasm/tsx.wasm +0 -0
- package/wasm/typescript.wasm +0 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Author: Muhammad Ishaq Khan
|
|
3
|
+
|
|
4
|
+
WHAT THIS DOCUMENT IS — the measured review of the indexer's file discovery, storage and derived phases, with each finding's evidence and status.
|
|
5
|
+
WHO IT IS FOR — maintainers of the indexing path and the gates that guard it.
|
|
6
|
+
EDIT TOGETHER WHEN THIS CHANGES — src/ast/parallel-indexer.ts, src/ast/index-space.ts, src/ast/index-lock.ts, and the gate scripts a finding names. A finding marked FIXED names the gate that now holds it.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Indexer review — time, space, scope, and the surface a user sees
|
|
10
|
+
|
|
11
|
+
Scope: `bb index` end to end on this repository. Written 2026-10-09 against
|
|
12
|
+
`analyzers/ontology-laptop-b`. Every claim below is either (a) measured on this machine in
|
|
13
|
+
this session, (b) read from the named file:line, or (c) produced by a focused review pass and
|
|
14
|
+
**re-verified by reading the code myself** before being written down. Where a claim was NOT
|
|
15
|
+
verified, it says so.
|
|
16
|
+
|
|
17
|
+
Method: the four review passes (discovery/scope, worker pool, store/DB, derived phases) each
|
|
18
|
+
read the real code paths and cited `file:line`; two of them additionally *executed* the real
|
|
19
|
+
functions (the discovery reviewer imported the actual `discoverIndexableFiles` and ran it
|
|
20
|
+
against synthetic fixtures; the pool reviewer reproduced Node's IPC behaviour with real child
|
|
21
|
+
processes). I then re-read the load-bearing findings myself. Measurements come from the
|
|
22
|
+
project's own `brain_index_status.performance_stats`, `dbstat`, and the run logs committed in
|
|
23
|
+
`validation/m60/results/` and `.tmp-probe/logs/`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. The measured baseline
|
|
28
|
+
|
|
29
|
+
Same command (`bb index . --force --no-setup`), same machine, before and after the recent work:
|
|
30
|
+
|
|
31
|
+
| | files | index time | whole command | worker kills | errors |
|
|
32
|
+
|---|---|---|---|---|---|
|
|
33
|
+
| before | 11,045 | 9,069 s (151 min) | — | 99, in bursts of 7 | 231 + 118 timeouts |
|
|
34
|
+
| after | 5,086 | 443.3 s (7 m 7 s) | 911 s (15.1 min) | 0 | 0 |
|
|
35
|
+
|
|
36
|
+
Phases recorded by the run itself (`brain_index_status.performance_stats`, last run):
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
discoveryMs 620 parseMs 372,264 (6.2 min)
|
|
40
|
+
workerStartupMs 241 dbFlushMs 404,176 (6.7 min) <-- now the largest
|
|
41
|
+
finalAccountingMs 490 postProcessingMs 67,417 (1.1 min)
|
|
42
|
+
totalWallMs 441,852 (7.4 min)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**The bottleneck moved from parsing to storing.** `dbFlush` now exceeds `parse`, and the two
|
|
46
|
+
overlap (their sum 776 s > wall 442 s). Store throughput implied: 687,099 symbol+relationship
|
|
47
|
+
rows written in 404 s ≈ **1,700 rows/s**. That is the number any further "blazing" claim has to
|
|
48
|
+
beat, and it is the reason §3 (space) and §6 (store path) matter more than parser work now.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Time complexity, phase by phase
|
|
53
|
+
|
|
54
|
+
| Phase | Cost model | Evidence |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| discovery/walk | O(F) stat + one `readdir` per directory; O(F²/B) for dispatch (see §5.13) | 620 ms for 5,086 files |
|
|
57
|
+
| parse (7 workers) | O(Σ parse cost per file); dominated by the largest files, not by F | 372 s |
|
|
58
|
+
| store (parent, synchronous) | O(S + R) rows, each with 18 index objects on `brain_relationship` (§3) | 404 s at ~1.7k rows/s |
|
|
59
|
+
| resolve | **linear plan**: three per-name aggregate temp tables + one indexed lookup per relationship. Fan-out 0.51 → 0.56 across 47→196 files (bound 1.5×) — but see §7.2: the reported "largest intermediate" excludes the plan's biggest table | 27,471 ms for 496,886 rels |
|
|
60
|
+
| enrich / gravity / taint / health | one pass each over stored relationships | 13,875 ms / ~0.5 s / 278 ms / 163 ms at 5,086 files |
|
|
61
|
+
| embeddings | O(embeddable symbols) network calls, background, capped | 24,502 queued vs a 25,000 cap; 2,283 stored; 7,014 still pending after a 300 s bounded wait |
|
|
62
|
+
|
|
63
|
+
**The gate's tiers do not cover production scale.** `script/perf/verify-scaling-linear.ts`
|
|
64
|
+
measures at 47/97/196 files and passes with 1.11× fan-out growth and 1.48× ms/rel growth
|
|
65
|
+
(bounds 1.5× / 2×). The production run resolves at **0.0553 ms/rel** — 2.6× the 0.0212 ms/rel
|
|
66
|
+
measured at 47 files, i.e. growth beyond what the 2× bound is tested over, across 108× the file
|
|
67
|
+
count. This is not proof of a superlinear algorithm (the per-rel cost also carries terms that
|
|
68
|
+
shrink relative to R), but it *is* a measurement the gate does not see, and a tier at ~1,000+
|
|
69
|
+
files belongs in it.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. Space complexity
|
|
74
|
+
|
|
75
|
+
The database is **3,177.4 MB** for 5,086 files / 190,213 symbols / 496,886 relationships /
|
|
76
|
+
27,280 embeddings. `dbstat` attribution:
|
|
77
|
+
|
|
78
|
+
| Object | Bytes | Note |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `brain_semantic_embedding` | **891.2 MB (28%)** | 27,280 rows = **34,254 B/row on disk** |
|
|
81
|
+
| `brain_relationship` + its **17** index objects | **1,077 MB** | table 328.5 MB, indexes **748.5 MiB** |
|
|
82
|
+
| `brain_symbol` + indexes | ~200 MB | `source_text` 23.9 MB over 36,946 rows (avg 132 B, cap 4,000) |
|
|
83
|
+
| free pages | **857.7 MB (27%)** | awaiting the next `--force` (VACUUM needs the write lock) |
|
|
84
|
+
|
|
85
|
+
**3.1 The vector is stored twice.** Every one of the 27,280 embedding rows has BOTH columns:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
embedding_vector (text) 662.1 MB = 25,450 B/row <- 2048 floats as JSON text
|
|
89
|
+
# NOTE (2026-11-01): this duplication is FIXED — dense vectors are stored once, as float32; the
|
|
90
|
+
# JSON column is empty for dense rows. Measured after the change on apollo-server: DB 84.5 -> 53.3 MiB
|
|
91
|
+
# with SUM(LENGTH(embedding_vector)) = 0. See validation/m14/verification/INDEXER-P1-STORAGE.md.
|
|
92
|
+
embedding_blob (blob) 209.5 MB = 8,053 B/row <- the same 2048 floats as float32
|
|
93
|
+
needed for float32 2048-d: 8,192 B
|
|
94
|
+
rows with BOTH populated: 27,280 of 27,280
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
So ~662 MB of the database — 21% of the whole file — is a second copy of vectors that already
|
|
98
|
+
exist as blobs. `src/brain/database.ts` comments that the blob is written for every dense vector
|
|
99
|
+
"so fresh indexes never need this [backfill]", which makes the text column pure duplication on
|
|
100
|
+
the write path as well: it is serialised, stored, indexed and read by nothing that needs it.
|
|
101
|
+
|
|
102
|
+
**3.2 Indexes cost more than the data they index.** `brain_relationship` carries **17** B-tree
|
|
103
|
+
objects (16 named indexes + the PK autoindex) for 496,886 rows: **748.5 MiB of indexes over a
|
|
104
|
+
328 MiB table (2.3×)**. The five largest alone (`autoindex_brain_relationship_1` 93.9,
|
|
105
|
+
`brain_rel_proj_type_target_idx` 81.0, `brain_rel_proj_target_idx` 77.5,
|
|
106
|
+
`brain_rel_proj_source_idx` 74.8, `brain_rel_target_type_idx` 58.0 MB) exceed the table. Every
|
|
107
|
+
insert maintains all 17 — this is the mechanical reason `dbFlushMs` is now the largest phase.
|
|
108
|
+
**Only three of the 17 are read during the load** (PK autoindex for dedup, `brain_rel_source_idx`,
|
|
109
|
+
`brain_rel_target_idx`), so **549 MiB of index is deferrable** to the end of a run; for
|
|
110
|
+
`brain_symbol`, 10 of 12 are deferrable (127 MB). The constraint is that deferral must be
|
|
111
|
+
implemented *inside* the run: `ensureHealthIndexes` recreates 8 core indexes on **every open** of
|
|
112
|
+
an existing database (`schema-ddl.ts:714-731` via `database.ts:85`) and `ensureQueryShapeIndexes`
|
|
113
|
+
recreates the gravity index (`:604-613`).
|
|
114
|
+
|
|
115
|
+
**3.3 Parent memory scales with the whole run, not the pool.** The parent retains every file's
|
|
116
|
+
full result — including per-symbol snippet text — in `results` for the entire run
|
|
117
|
+
(`parallel-indexer.ts:1490`, pushed on every outcome, cleared only after the pool resolves at
|
|
118
|
+
`:5844`; `symbols` carry snippets of up to 10,000 chars, `pool-worker.ts:1047`). Measured by the
|
|
119
|
+
pool reviewer with real child processes: while the parent was blocked in a synchronous store, a
|
|
120
|
+
child's RSS climbed 75 → 120 → 215 → 306 MB sending 300 × 1 MB messages, and the parent then
|
|
121
|
+
allocated 399 MB receiving them. There is no IPC high-water mark in either direction
|
|
122
|
+
(`safeSend` discards `process.send`'s boolean, `pool-worker.ts:94-103`). For a 1M-symbol project
|
|
123
|
+
this is the failure mode that ends the run, and it is a *space* bound driven by total symbols.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Discovery and scope — what is indexed versus what exists
|
|
128
|
+
|
|
129
|
+
Measured against `git ls-files` (12,699 tracked) and the real index (5,086 files):
|
|
130
|
+
|
|
131
|
+
| tree | tracked | indexed | coverage |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `src` | 209 | 205 | **98.1%** |
|
|
134
|
+
| `script` | 4,297 | 3,906 | 90.9% |
|
|
135
|
+
| `docs` | 291 | 288 | 99.0% |
|
|
136
|
+
| `test` | 42 | 42 | 100% |
|
|
137
|
+
| `website` | 124 | 92 | 74.2% |
|
|
138
|
+
| `ide` | 36 | 32 | 88.9% |
|
|
139
|
+
| `validation` | 6,580 | 214 | 3.3% (`.bbignore`, deliberate) |
|
|
140
|
+
| `grammars` | 286 | 0 | 0% (`.bbignore`, deliberate) |
|
|
141
|
+
| `worktree-snapshots` | 286 | 0 | 0% (`.bbignore`, deliberate) |
|
|
142
|
+
| `.tmp-probe` | 144 | 0 | 0% (`.bbignore`, deliberate) |
|
|
143
|
+
|
|
144
|
+
Findings, all read from the code (the discovery pass additionally executed the real walk):
|
|
145
|
+
|
|
146
|
+
1. **CRITICAL — directory-name skips beat every ignore file and cannot be re-included.**
|
|
147
|
+
`shouldSkip("/" + relativePath)` runs at `parallel-indexer.ts:1033`, *before*
|
|
148
|
+
`localIg.ignores(...)` at `:1035`, and `shouldSkip` is a bare
|
|
149
|
+
`skipPatterns.some(p => p.test(path))` (`:636-638`) with no negation. The patterns include
|
|
150
|
+
whole segment names: `env` (`:270`), `bin` (`:298`), `out` (`:274`), `deps` (`:293`),
|
|
151
|
+
`gen`/`generated` (`:312-313`), `platforms` (`:307`), `build` (`:273`), `target` (`:275`),
|
|
152
|
+
`perf` (`:337`). Verified by running the real walk: `src/env/config.ts`,
|
|
153
|
+
`src/bin/tool.ts`, `src/out/box.tsx`, `src/gen/api.ts` are all absent — and no `.bbignore`
|
|
154
|
+
line can bring them back. Rust `src/bin/*.rs`, `packages/ui/src/out/Box.tsx` and
|
|
155
|
+
`src/deps/container.ts` are permanently invisible.
|
|
156
|
+
2. **HIGH — `.mts` and `.cts` are never indexed.** `EXTENSION_LANGUAGE`
|
|
157
|
+
(`language-detection.ts:20-103`) maps `ts, tsx, js, cjs, mjs, jsx` and has no `mts`/`cts`
|
|
158
|
+
(verified myself). **490 tracked files** in this repository are `.mts`, including much of the
|
|
159
|
+
gate suite (`script/m14/clean-*.mts`, `script/m12/xml-*.mts`). Same gate also drops `.hh`,
|
|
160
|
+
`.tpp`, `.ipp`, `.inl`, `.inc`, `.csproj`, `.fsproj`, `.xaml`, `.plist`, `.wsdl`, `.pom`,
|
|
161
|
+
`.conf`, `.ini`, `.cfg`, and hand-written `.d.ts` (`:320`) — the last of which *routes* but is
|
|
162
|
+
then skipped. `ParallelIndexerConfig.extensions` (`:145-251`) is dead code: nothing reads it,
|
|
163
|
+
and it disagrees with the map that actually admits files.
|
|
164
|
+
3. **HIGH — the 1–5 MB tier reads only the first 256 KB, silently.** `pool-worker.ts:920-935`
|
|
165
|
+
allocates `min(size, 256 KB)`, hashes the *truncated* text, and returns
|
|
166
|
+
`extractFallback(...)`. The "Using structural extraction for large file" message goes to
|
|
167
|
+
`function log(..._args: unknown[]) {}` (`pool-worker.ts:36`) — **a no-op logger** (verified).
|
|
168
|
+
Worse, the read-side freshness check hashes the whole file (`freshness.ts:17,323`), so any
|
|
169
|
+
metadata change makes such a file report permanently `hash-differs` with a reindex
|
|
170
|
+
remediation that cannot clear it.
|
|
171
|
+
4. **HIGH — symlinks are never followed.** `isSymbolicLink` appears zero times in `src`; the walk
|
|
172
|
+
only consults `isDirectory()`/`isFile()` (`:1006`, `:1038-1040`). Verified: a symlinked file
|
|
173
|
+
and a symlinked directory are both absent (and a cyclic link cannot hang — it is never
|
|
174
|
+
entered).
|
|
175
|
+
5. **HIGH — nested ignore files are flattened, so anchored patterns are inert.** Child
|
|
176
|
+
`.gitignore`/`.bbignore` files are added into one root `ignore()` instance (`:976-997`) but
|
|
177
|
+
tested with root-relative paths (`:1035`). Verified: `sub/.gitignore` containing `/build2/`
|
|
178
|
+
and `/anchored.ts` left both files indexed.
|
|
179
|
+
6. **HIGH — submodules and worktrees are indexed as first-party.** The nested-repo check tests
|
|
180
|
+
`e.isDirectory() && e.name === ".git"` (`:972-974`); a submodule or worktree writes `.git` as
|
|
181
|
+
a FILE, so the subtree is walked (verified) and its symbols enter the graph.
|
|
182
|
+
7. **HIGH — a file that grows past 5 MB keeps its old rows and is reported "unchanged".**
|
|
183
|
+
`foundFiles.add()` (`:918`) happens before the >5 MB return (`:921-925`), and stale cleanup
|
|
184
|
+
only removes paths absent from `foundFiles` (`:1088-1089`); `skippedFiles++` is then reported
|
|
185
|
+
as `unchanged` (`:5719`; CLI prints it at `cli/index.ts:416`).
|
|
186
|
+
8. **HIGH — no stable ordering, and it reaches a page.** Nothing sorts the walk (`readdir` →
|
|
187
|
+
`await stat` → push, `:944-955`), and `symbol-search.ts:323,327` applies `LIMIT` with **no
|
|
188
|
+
ORDER BY**, so which rows survive a candidate cap is insertion order. Symbol IDs are
|
|
189
|
+
content-addressed, so stored rows are reproducible; the *page* is not.
|
|
190
|
+
9. **HIGH — the in-process (no-fork) mode silently truncates.** `FULL_AST_CUTOFF = 13000`,
|
|
191
|
+
`MAX_FILES_IN_PROCESS = 14000` (`:1169-1174`): files past 14,000 are never parsed, files past
|
|
192
|
+
13,000 are regex-extracted, and the summary's arithmetic cannot account for them
|
|
193
|
+
(`:5324`, residual at `:5720`); the CLI never prints `byLanguage` or fallback counts.
|
|
194
|
+
10. **HIGH — no duplicate-content detection.** Identity is the path
|
|
195
|
+
(`INSERT OR REPLACE INTO brain_file` keyed on `projectId:relativePath`, `:2752-2757`); the
|
|
196
|
+
hash column is a *non-unique* index (`brain.sql.ts:29,48,52`) and `fileHash` is never used to
|
|
197
|
+
dedupe. A copied or vendored file is stored twice — duplicate symbols, duplicate embeddings,
|
|
198
|
+
and `bb_edit`'s "symbol is ambiguous" path becomes reachable.
|
|
199
|
+
11. **HIGH — read-time exclusions hide files the index deliberately kept.** `NOISE_DIR_SEGMENTS`
|
|
200
|
+
(`analysis-exclusions.ts:7-21`) hides `fixtures`, `vendor`, `snapshots`, `baselines`,
|
|
201
|
+
`generated`, `gen` from every read surface while the walk indexes them. The reviewer
|
|
202
|
+
demonstrated it live in this repo: `bb_search mode=file` returns
|
|
203
|
+
`script/fixtures/contract-fixture.ts` as indexed and fresh, while a symbol search for its
|
|
204
|
+
export `createContractFixture` answers "no results … scan was cut short (noise-filter)" —
|
|
205
|
+
despite three tracked scripts importing it.
|
|
206
|
+
12. **MEDIUM — data formats are admitted by name, bounded only by size.** json/yaml/toml/xml/
|
|
207
|
+
svg/md/sql/graphql all route; lock files are named one by one (`:322-325`) so `Cargo.lock`
|
|
208
|
+
and `Gemfile.lock` are excluded by accident while `poetry.lock`/`Pipfile` are indexed as
|
|
209
|
+
TOML. Measured in this repo before the `.bbignore` work: json was 78.8% of all symbols.
|
|
210
|
+
13. **MEDIUM — three more walks with divergent skip lists.** `fingerprint.ts:79-80` uses the union
|
|
211
|
+
of `skipPatterns` + `cliSkipPatterns` (which uniquely has `snapshot.json$`), so
|
|
212
|
+
`computeCheckoutFingerprint` can never equal `computeDbFingerprint` for a tree containing
|
|
213
|
+
such a file — "moved project" adoption never fires. The third list was `collectDirChanges`'
|
|
214
|
+
`DIR_WALK_SKIP_DIRS`, which included `vendor` that discovery deliberately does *not* skip;
|
|
215
|
+
that divergence is removed (see finding 14) and the deletion rule no longer depends on walk
|
|
216
|
+
absence at all.
|
|
217
|
+
14. **FIXED (was HIGH) — `bb_index mode=dir` deleted what its own walk did not see.** The walk's
|
|
218
|
+
absence used to be treated as deletion: every stored path under the prefix that the walk did
|
|
219
|
+
not return became `{type:"deleted"}` and was purged. `vendor` was in the walk's skip set while
|
|
220
|
+
discovery deliberately indexes it, so editing one file in a tree containing `vendor/` silently
|
|
221
|
+
purged that code's symbols and reported a normal-looking `deleted: N` — measured on an isolated
|
|
222
|
+
fixture as files 3→1. Two rules now hold: a row is marked deleted ONLY when the file is
|
|
223
|
+
provably absent from disk (stat ENOENT), and the walk's skip set matches discovery's (`vendor`
|
|
224
|
+
walked). Proof: `validation/tools/bb_index/audit/door-vendor.json` (before: deleted 2, files
|
|
225
|
+
3→1) versus `audit/door-vendor-after-fix.json` (after: deleted 0, files 3→3), and the mutant in
|
|
226
|
+
`script/m40/verify-a3-write-parity.ts` (`BB_M40_MUTATE_A3_WALK_DELETE=1`) goes red.
|
|
227
|
+
15. **MEDIUM — explicit-file indexing bypasses ignore files.** `:5304-5305` filters only
|
|
228
|
+
`shouldSkipCli` + `hasValidExtension`; `:6993-6994` only `hasValidExtension` + `shouldSkip`.
|
|
229
|
+
A gitignored file added by path enters the index and is purged again by the next full run.
|
|
230
|
+
16. **MEDIUM — stored text is capped twice, silently**: 10,000-char snippets
|
|
231
|
+
(`pool-worker.ts:1047`) and `source_text` at 4,000 chars / 200 lines (`:733-751`, applied
|
|
232
|
+
`:3698`). Content search cannot find a term that appears late in a large symbol.
|
|
233
|
+
17. **MEDIUM — no binary-content detection.** Any name-routed file is parsed; the only content
|
|
234
|
+
probe is the 256-byte shebang check for extensionless files (`:692-710`). A misnamed binary
|
|
235
|
+
becomes a symbol-less `brain_file` row counted as `symbolLoss`.
|
|
236
|
+
18. **LOW — name rules catch hand-written files**: `.*-min\.` and `.*\.min\.` (`:315-316`) exclude
|
|
237
|
+
`admin-min.ts`; `.*\.map$` (`:319`) excludes any `foo.map`; depth > 50 returns silently
|
|
238
|
+
(`:967`); `localIg.ignores` is written twice (`:1035-1036`) and `/obj/` twice
|
|
239
|
+
(`:1022-1023`); the inline compiled-mode list (`:1013-1028`) is redundant with
|
|
240
|
+
`skipPatterns` + `rootIg` and is inert at root level.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 5. The worker pool
|
|
245
|
+
|
|
246
|
+
Architecture: the parent forks workers; workers parse and `process.send` results; **the parent is
|
|
247
|
+
the only process that touches SQLite**, synchronously. Verified findings:
|
|
248
|
+
|
|
249
|
+
1. **HIGH — no `error` listener on pool workers, so a send to a dying worker can kill the parent.**
|
|
250
|
+
The only `.on("error")` in 7,083 lines is on the fork *probe's* throwaway worker (`:5486`) —
|
|
251
|
+
verified myself. `child.send()` to a closed channel emits an unhandled `'error'`
|
|
252
|
+
asynchronously, so the `try/catch` at `:2034-2064` does not catch it. The reviewer reproduced
|
|
253
|
+
both orderings on Node v22.22.1 with real children: after the `exit` event, send returns false
|
|
254
|
+
*and* the process exits 1 with `ERR_IPC_CHANNEL_CLOSED`; in the pool's actual ordering — the
|
|
255
|
+
parent blocked 400 ms across the child's death, with `exitCode===null, killed===false,
|
|
256
|
+
connected===true` (exactly the state the guard reads as alive) — send returns **true** and the
|
|
257
|
+
write fails with EPIPE, exiting 7. This is the same failure class the comment at `:1579-1606`
|
|
258
|
+
claims to have fixed. One line fixes it: `worker.on("error", …)` at fork time.
|
|
259
|
+
2. **HIGH — a worker that dies before `ready` bypasses the restart cap and forks forever.**
|
|
260
|
+
The `exit` handler calls `clearStartupTimer(worker)` first (`:1881-1883`), so the startup
|
|
261
|
+
watchdog can never fire for a fast death; `startupFailures` is incremented only at `:1739`,
|
|
262
|
+
`:1754`, `:1775`, `:1934` — never on `exit` (verified). With `MAX_STARTUP_FAILURES` never
|
|
263
|
+
reached, the replacement branch (`:1899-1916`) loops fork → die → fork with nothing parsed and
|
|
264
|
+
nothing reported. The *silent*-worker variant is capped; that is the only case the existing
|
|
265
|
+
fixture covers.
|
|
266
|
+
3. **MEDIUM — no IPC flow control; the parent retains the whole run in memory** (see §3.3).
|
|
267
|
+
4. **MEDIUM — an engine-failure worker is pooled twice.** `pool-worker.ts:1528-1530` sends
|
|
268
|
+
`engine_fail` and then `:1545-1554` sends `ready` unconditionally; the parent pushes on both
|
|
269
|
+
(`:1812-1830`) and dispatch shifts from `idleWorkers` without a membership check (`:2003-2015`),
|
|
270
|
+
so one process can run two batches — halving effective parallelism on exactly the regex-fallback
|
|
271
|
+
path, against unchanged 45 s budgets.
|
|
272
|
+
5. **MEDIUM — the startup watchdog has no stall deferral.** `decideWatchdog` is applied only to
|
|
273
|
+
per-file timeouts (`:1619-1638`); the startup timer kills on wall clock with the sole test
|
|
274
|
+
`workers.has(worker)` (`:1769-1777`), so a parent block longer than the startup timeout can
|
|
275
|
+
SIGKILL a worker that already sent `ready`, and each such kill advances `startupFailures`
|
|
276
|
+
toward failing the remaining queue outright.
|
|
277
|
+
6. **MEDIUM — `processed` counts attempts, not files.** Ten `processed++` sites (verified: `:1187`,
|
|
278
|
+
`:1232`, `:1655`, `:1669`, `:1684`, `:1696`, `:1730`, `:1856`, `:1862`, …), including timeout,
|
|
279
|
+
poison-drop and retry paths; the percentage is `processedFiles/totalFiles*98`
|
|
280
|
+
(`:5536`) with no clamp, and `filesPerSecond` uses the same counter (`:5855`). A run with
|
|
281
|
+
retries can therefore report >100%.
|
|
282
|
+
7. **LOW — a `file_done` for an already-killed batch is still accepted** (`:1838-1857` never
|
|
283
|
+
consults `pendingBatches`, unlike `file_start` `:1832` and `batch_done` `:1865`), so a file can
|
|
284
|
+
be parsed and stored twice after a timeout kill. No data loss (`INSERT OR REPLACE`); CPU and
|
|
285
|
+
counters suffer.
|
|
286
|
+
8. **LOW/MEDIUM — a dispatch into a dead-but-unreaped worker is dropped by the kernel**, and the
|
|
287
|
+
ensuing timeout blames `batch.files[0]` as a poison pill (`:1678-1704`). The end-of-run DB pass
|
|
288
|
+
usually rescues it (`:5740-5748`) — but that pass is skipped on a cancelled run (`:5737-5740`).
|
|
289
|
+
*Positive:* no reachable path leaves a file with zero result entries, and the batch retry loops
|
|
290
|
+
are bounded (`MAX_FILE_RETRIES = 3`, `:1557-1558`, checked on every requeue).
|
|
291
|
+
9. **MEDIUM — `new Promise(async (resolve) => …)` has no catch** (`:1489`): a synchronous throw
|
|
292
|
+
from `fork()` (EMFILE/ENOMEM) leaves the pool promise pending forever, with no diagnostic and
|
|
293
|
+
live children holding IPC handles.
|
|
294
|
+
10. **LOW — pool size ignores cgroup/affinity limits.** `Math.max(2, Math.min(cpus().length - 1, 12))`
|
|
295
|
+
(`:125`) gives **2 workers on a 1-core machine** (`min(0,12)=0` → `max(2,0)`), and a 2-CPU
|
|
296
|
+
container on a 64-core host reports 63 → caps at 12. `os.availableParallelism()` is
|
|
297
|
+
affinity-aware; `cpus()` is not. There is no CLI or env override for concurrency.
|
|
298
|
+
11. **LOW — an empty file list still forks a worker** (`:1387` `|| 1`, then `:2068-2070`), costing
|
|
299
|
+
one Node/tsx boot (0.55–3.4 s) on a no-op run.
|
|
300
|
+
12. **LOW — a >5 MB file is recorded as `success: true` with zero symbols** and an empty-string
|
|
301
|
+
hash (`pool-worker.ts:897-916`); it is only revisited if mtime/size change.
|
|
302
|
+
13. **INFORMATIONAL — dispatch is O(F²/B) worst case**: `fileQueue.splice(0, batchSize)` copies the
|
|
303
|
+
remaining tail per dispatch (`:2015`), and retries `unshift` (`:1674`, `:1701`, `:2058`).
|
|
304
|
+
Irrelevant below ~100k files; the dominant term is memory (§3.3).
|
|
305
|
+
14. **NO DEFECT — dispatch is work-conserving.** Every event that can idle a worker calls
|
|
306
|
+
`scheduleNext()` synchronously, and there is deliberately no timer-based dispatch
|
|
307
|
+
(`:1911-1913`) because a blocked loop would never fire it.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 6. The store path and the database
|
|
312
|
+
|
|
313
|
+
`storeResults` (`parallel-indexer.ts:2727`) prepares **13 statements once per call** and executes
|
|
314
|
+
one `.run()` per row — no multi-row `VALUES` anywhere, though better-sqlite3 allows 32,766
|
|
315
|
+
variables. One transaction per call (`:3003`, invoked `:4326`, default `BEGIN DEFERRED`; the first
|
|
316
|
+
statement per file is a DELETE, so the write lock is taken at the top). Per file, in order: six
|
|
317
|
+
DELETEs (`:3059-3066`) → `insertFile` (`:3163`) → the symbol loop (`:3654`) → the relationship
|
|
318
|
+
loop (`:3984`/`:4281`).
|
|
319
|
+
|
|
320
|
+
**6.1 Batch sizes have drifted apart.** `workerBatchSize` is 10 files per IPC batch (`:136`); the
|
|
321
|
+
pool's DB flush threshold is **hardcoded 20** (`:1850`); `FLUSH_CHUNK_FILES` is 20 (`:1453`);
|
|
322
|
+
and `dbBatchSize: 100` (`:139`), documented as the bulk-insert bound, **never binds in the
|
|
323
|
+
worker-pool path** because `storeResults` receives at most 20 files per call. So the pool commits
|
|
324
|
+
more often, and writes more WAL frames, than the configuration implies.
|
|
325
|
+
|
|
326
|
+
**6.2 A SELECT is executed PER RELATIONSHIP, and it sorts every candidate.** Call site
|
|
327
|
+
`:4119-4122`; statements `resolveTargetStmt` (`:2856-2862`) and `resolveContainsTargetStmt`
|
|
328
|
+
(`:2971-2977`), both shaped:
|
|
329
|
+
|
|
330
|
+
```sql
|
|
331
|
+
SELECT s.id, s.file_id, f.language FROM brain_symbol s
|
|
332
|
+
LEFT JOIN brain_file f ON f.id = s.file_id
|
|
333
|
+
WHERE s.project_id = ? AND s.name = ?
|
|
334
|
+
ORDER BY COALESCE(s.is_declaration,0) ASC, (s.file_id = ?) DESC, (f.language = ?) DESC, s.id
|
|
335
|
+
LIMIT 1
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`EXPLAIN QUERY PLAN` on the real index reports `SEARCH s USING INDEX
|
|
339
|
+
brain_sym_proj_name_type_idx` … **`USE TEMP B-TREE FOR ORDER BY`** — every edge materialises and
|
|
340
|
+
sorts *all* same-named candidates. Measured per query: **0.146 ms at 0 candidates → 0.229 at 5 →
|
|
341
|
+
0.663 at 55 → 6.506 at 553 → 11.538 at 2,010 → 21.342 ms at 3,159**. Distribution across the
|
|
342
|
+
496,886 edges (proxy: `target_external`): **26,838 edges see ≥1,000 candidates**, 67,814 see
|
|
343
|
+
100–999, 115,083 see 10–99, 162,440 see 1–9, 124,711 see none. The same information is already in
|
|
344
|
+
memory (`nameToSymbol`, `:3246`, `:3367-3391`).
|
|
345
|
+
|
|
346
|
+
**Honest bound on this finding.** The additive estimate from those buckets (~14.7 min) *exceeds*
|
|
347
|
+
the measured 404 s store phase, so the `target_external` proxy over-counts (about 20% of targets
|
|
348
|
+
are qualified, and the leaf differs). The 404 s is the real ceiling for the sum of this SELECT,
|
|
349
|
+
17-way index maintenance and the per-file DELETEs; the split among them is **not measured** and
|
|
350
|
+
would need instrumentation.
|
|
351
|
+
|
|
352
|
+
**6.3 Failure semantics are the dangerous part.**
|
|
353
|
+
|
|
354
|
+
1. **CRITICAL — a full disk is invisible.** There is no `SQLITE_FULL`/`ENOSPC` path anywhere in
|
|
355
|
+
`src/`. A failing commit is caught, rolled back and **skipped** (`:4330-4343`); the run reaches
|
|
356
|
+
its status write and summary, whose counts are re-read from the database
|
|
357
|
+
(`:5847-5849`; `cli/index.ts:299-302`). The user sees `✓ indexed N files…` with N silently
|
|
358
|
+
smaller.
|
|
359
|
+
2. **HIGH — a broken index can be persisted as `complete`.** `status = errorFiles > 0 ? "partial"
|
|
360
|
+
: "complete"` (`:5906`), but `errorFiles` only counts *parse* outcomes (`:5694`, `:5812`); a
|
|
361
|
+
store rollback is written to stderr and nothing else (`:4335`, `:1971-1976`).
|
|
362
|
+
3. The end-of-run checkpoint fires **before** the embedding drain finishes (`cli/index.ts:303-307`
|
|
363
|
+
vs the drain awaited at `:317-333`), so "the writes have stopped here" is false and the WAL
|
|
364
|
+
regrows after the `index space:` line.
|
|
365
|
+
4. The force-wipe is **not transactional**: nine autocommit DELETEs with `foreign_keys = OFF`
|
|
366
|
+
(`:6168-6194`). A crash mid-wipe leaves a partially wiped index that nothing marks suspect.
|
|
367
|
+
5. Corruption surfaces as one opaque driver line (`db.node.ts:22-23` → `cli/index.ts:1288-1292`),
|
|
368
|
+
and additive migrations are blanket `catch {}` (`schema-ddl.ts:569-576, 615-622, …`), so a
|
|
369
|
+
corrupt database can be silently left un-migrated.
|
|
370
|
+
|
|
371
|
+
**6.4 Pragmas are minimal and the temp store is on disk.** Only `journal_mode=WAL`,
|
|
372
|
+
`busy_timeout=5000`, `synchronous=NORMAL`, `foreign_keys` are set (`db.node.ts:29-35`).
|
|
373
|
+
`cache_size`, `mmap_size`, `temp_store` and `journal_size_limit` are never set, so the driver's
|
|
374
|
+
compiled defaults apply: **16 MB page cache, mmap off, `TEMP_STORE=1` (file-backed), 4 MB
|
|
375
|
+
auto-checkpoint**. For a 3.3 GB database whose store path forces a temp sort per edge, that is the
|
|
376
|
+
un-tuned part of the load — and it is why the `USE TEMP B-TREE` in 6.2 lands on disk.
|
|
377
|
+
|
|
378
|
+
**6.5 Concurrency and locking are mostly right, with one hole.** A second indexer is refused
|
|
379
|
+
immediately by an `O_EXCL` lockfile naming the holder (`index-lock.ts:96-122`, taken before the
|
|
380
|
+
`--force` wipe at `cli/index.ts:154-166`). The in-place wipe is verified to keep a live MCP
|
|
381
|
+
server's cached handle valid — and VACUUM is verified to work while another process holds the
|
|
382
|
+
database open (it needs the *write* lock, not exclusivity). The hole: **the MCP's own writers hold
|
|
383
|
+
no index lock** — the background embedding drainer (`:2496-2690`) and annotations — so they contend
|
|
384
|
+
only through `busy_timeout=5000`, and a lost embedding chunk is counted `failed` and downgraded to
|
|
385
|
+
tf-idf (`:2648-2656`).
|
|
386
|
+
|
|
387
|
+
**6.6 FIXED — reclamation ran on no path, not just the CLI one.** `index-space.ts` issued its
|
|
388
|
+
PRAGMAs through `rawDb.pragma`, but every caller holds the `CoreDatabase` wrapper, which exposes
|
|
389
|
+
`native`/prepare/exec and no `pragma` — so `checkpointWal` threw inside its own try/catch and
|
|
390
|
+
returned `false` on every runtime, while `spaceReport` read a zero freelist. The MCP `bb_index`
|
|
391
|
+
path additionally never called it at all. Both are fixed: PRAGMAs are issued through the driver's
|
|
392
|
+
own method (or a prepared statement), and a successful reindex now runs a WAL checkpoint and
|
|
393
|
+
reports `walCheckpointed`. Evidence: `validation/tools/bb_index/audit/door-t0-postfix.json`
|
|
394
|
+
(`walCheckpointed: true`) and `script/perf/verify-space-reclaim.ts`.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 7. Derived phases, and where the candidate cost actually went
|
|
399
|
+
|
|
400
|
+
The bulk plan really is the linear one, and it is measured. `stats = { plan: "linear", … }`
|
|
401
|
+
(`:4939`); the plan builds `_i13_src` (`:4827`), `_i13_tier` (`:4854`), `_i13_best_file` (`:4865`),
|
|
402
|
+
`_i13_best_lang` (`:4877`), `_i13_best_any` (`:4888`), `_i13_pick` (`:4910`) and applies with one
|
|
403
|
+
`UPDATE … FROM` (`:4945-4951`). `ROW_NUMBER`/"window" survives only in probes. Measured on the real
|
|
404
|
+
index (496,886 relationships, 190,213 symbols): `_i13_src` 1,600 ms · `_i13_tier` 155 ·
|
|
405
|
+
`_i13_best_file` 304 · `_i13_best_lang` 381 · `_i13_best_any` 173 · `_i13_pick` 2,033 ·
|
|
406
|
+
**apply 33,433 ms**. The apply dominates because it rewrites rows in a table carrying 17 indexes.
|
|
407
|
+
What makes the shape safe is the unique index on `_i13_src(rel_id)` (`:4852`) — without it the plan
|
|
408
|
+
degenerates to a scan per relationship (measured 15.6 s vs 0.060 s).
|
|
409
|
+
|
|
410
|
+
**7.1 The candidate cost did not disappear — it moved into the store.** `resolveTargetStmt` and
|
|
411
|
+
`resolveContainsTargetStmt` (`:2856-2862`, `:2971-2977`) run **once per relationship at insert
|
|
412
|
+
time** (`:4119-4122`), and that call site is **not** inside the `if (!options.skipResolution)`
|
|
413
|
+
block (which spans `:4368-4555`). Verified: the pool path stores every batch with
|
|
414
|
+
`skipResolution: true` (`:1465`) and still pays the per-edge SELECT for all 496,886 edges. So the
|
|
415
|
+
complexity is O(R × C log C), and it lands in `dbFlushMs` (404 s of the 442 s run), not in
|
|
416
|
+
`postProcessingMs`. Measured C on the live index: **average 3.66, max 3,159; 237 names have ≥100
|
|
417
|
+
candidates and 14 have ≥1,000**. This is the single largest algorithmic term in the indexer today,
|
|
418
|
+
and it is exactly the term the linear plan was written to remove from the other pass.
|
|
419
|
+
|
|
420
|
+
**7.2 The gate cannot see the plan's biggest intermediate.** The reported value is
|
|
421
|
+
`MAX(c)` over `_i13_tier`, `_i13_best_file`, `_i13_best_lang`, `_i13_best_any` and `_i13_pick`
|
|
422
|
+
(`:4930-4938`) — it **excludes `_i13_src`**, which is R rows (496,886 here against a reported
|
|
423
|
+
246,909). The comment above it says "the largest intermediate it materialised, counted from the
|
|
424
|
+
tables it built", and `verify-scaling-linear.ts` divides this number by R to compute the clock-free
|
|
425
|
+
fan-out check. A table of R rows is dropped from a metric whose purpose is to bound the largest
|
|
426
|
+
intermediate, so the gate's headline number under-states it by roughly 2× on this corpus.
|
|
427
|
+
|
|
428
|
+
**7.3 Resolution is 6–8 passes over R, not one.** Between them: the unresolved `COUNT`
|
|
429
|
+
(`:4602-4604`), two strategy `UPDATE`s (`:4641`, `:4655`), the dotted-target update (`:4443`), the
|
|
430
|
+
import resolver (`:4690`), `_i13_src` (`:4827`), `_i13_pick` (`:4910`), the `is_cross_file` recompute
|
|
431
|
+
(`:4956-4966`) and cross-language normalisation with two correlated `EXISTS` per resolved row
|
|
432
|
+
(`:4979-5006`, measured 11.1–13.7 s for 496,886 rows).
|
|
433
|
+
|
|
434
|
+
**7.4 The other derived phases are sound.** Enrichment is O(R) keyset pages with one PK update per
|
|
435
|
+
row (`brain-relationship-enrichment.ts:180-728`); gravity is O(S log R) with two indexed probes per
|
|
436
|
+
symbol plus five whole-table updates (`:43-117`, measured 3.4–4.1 s for 190k symbols); health is
|
|
437
|
+
four aggregates (`project-health.ts:48-169`); taint is one CTE plus per-flow updates. **No derived
|
|
438
|
+
phase is O(R×N).** Two blemishes: taint's `LIMIT 10000` (`:5071`) truncates without a truncation
|
|
439
|
+
flag, and the function swallows query errors and returns 0 (`:5147-5150`), so a failed taint pass is
|
|
440
|
+
recorded as an "ok" phase outcome.
|
|
441
|
+
|
|
442
|
+
**7.5 The plan's largest intermediate is on disk.** No `PRAGMA temp_store` is ever set
|
|
443
|
+
(§6.4), so `_i13_src` — R rows — is a **file-backed** temp table, and the per-edge `ORDER BY` in
|
|
444
|
+
§7.1 sorts through disk-backed temp storage too. On a 3.3 GB database with a 16 MB page cache, that
|
|
445
|
+
is the un-tuned part of every sort in this review.
|
|
446
|
+
|
|
447
|
+
**7.6 ANALYZE runs once, late, and stale stats are never repaired.** `ANALYZE` runs at the end of
|
|
448
|
+
`runPostProcessing` (`:6472-6480`) — not in the bounded incremental pass (`:6666-6842`), not when
|
|
449
|
+
the run is cancelled (`:5916-5917`), and not with `--skip-post-processing`. `ensurePlannerStats`
|
|
450
|
+
(`schema-ddl.ts:753-764`) only fires when `sqlite_stat1` is **absent**, on the pre-existing-DB
|
|
451
|
+
branch, so present-but-stale statistics are never refreshed on the read path and nothing reports
|
|
452
|
+
that a project maintained by `--changed`/`--resume`/`bb_edit` is planning on statistics that
|
|
453
|
+
describe an older database. The three documented blowups from missing stats (`_i13_src` 260×,
|
|
454
|
+
taint 600×, graph owner-cascade 200–400×) are data-skew dependent; the reviewer reproduced the plan
|
|
455
|
+
changes, not the blowups.
|
|
456
|
+
|
|
457
|
+
**7.7 `--skip-post-processing` can report a healthy index that never had a derived pass.** The run
|
|
458
|
+
persists `status: "complete"` (`:5900-5910`, `:6045-6056`) although nothing derived ran, and
|
|
459
|
+
`bb_health`'s "Index Completeness" check reads exactly that row (`brain-index-health.ts:287-360`) —
|
|
460
|
+
so a deferred index can read HEALTHY. The CLI's own "consistency" line counts only rows with no
|
|
461
|
+
external identity on either end (`:5194-5214`); the **34,646 rows on this repo that carry
|
|
462
|
+
`target_external` and a NULL target** are not counted, so it can print "every internal relationship
|
|
463
|
+
is attached to a symbol" over tens of thousands of unattached internal edges. The `--post` help
|
|
464
|
+
lists five phases; the code records eight (`:6353-6480`).
|
|
465
|
+
|
|
466
|
+
**7.8 Embeddings: capped by product design, invisible until the summary.** The queue cap is 25,000
|
|
467
|
+
(`BB_EMBEDDING_QUEUE_LIMIT`, `:2110`) and a single batch over `BB_EMBEDDING_BATCH_LIMIT` (50,000) is
|
|
468
|
+
dropped whole (`:2453-2457`). Overflow drops are logged at **debug** level only (`:2462`) and
|
|
469
|
+
surface solely in the end-of-run line "N dropped (queue cap)". The drainer is serial, chunked at 100
|
|
470
|
+
vectors, one transaction per chunk, with a tf-idf fallback for the rest (`:2662-2704`); it never
|
|
471
|
+
blocks the index but keeps the process alive (measured ~10 min of silent tail, now announced and
|
|
472
|
+
bounded). Two models in one index is **by design** (`:2143-2147`); search filters by the *query's*
|
|
473
|
+
model (`semantic-search.ts:191`) and falls back to keyword search with reason
|
|
474
|
+
`no-matching-model-embeddings`, so the other model's vectors are invisible rather than merged. The
|
|
475
|
+
summary warns about it (`cli/index.ts:368-380`).
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 8. Progress, ETA, and the messages a user reads
|
|
480
|
+
|
|
481
|
+
All from `src/cli/index-progress.ts` (313 lines) plus the indexer's own framing line.
|
|
482
|
+
|
|
483
|
+
1. **The ETA is a cumulative average and ignores everything after parsing.**
|
|
484
|
+
`estimateRemainingMs` (`:116-124`) is `(elapsed / processedFiles) × remaining`, with a single
|
|
485
|
+
guard `elapsed < 1000 ms`. Consequences measured in this session's own logs: the ETA slid
|
|
486
|
+
33m → 25m → 19m → **8m** → 25m → 14m → 20m as the file mix changed, and at `100%` it prints
|
|
487
|
+
`eta=0s` while the derived phases and the embedding tail still lie ahead — in the measured
|
|
488
|
+
run, files finished at 5 m 33 s and the command ended at 15.1 min, i.e. the ETA called it
|
|
489
|
+
done two-thirds of the way early. It also inherits the attempt-vs-file counter (§5.6), so a
|
|
490
|
+
run with retries can show >100%. A windowed rate and a phase-aware remainder (parse vs
|
|
491
|
+
derived vs embedding queue) would both be honest; today's number is not.
|
|
492
|
+
2. **The size bands exist and are sensible — except at the small end.**
|
|
493
|
+
`expectationFor` (`:100-105`): 0 → "nothing to index — the index is already up to date",
|
|
494
|
+
<150 → "this is a small project — a few minutes at most", <4,000 → "this will take a few
|
|
495
|
+
minutes", ≥4,000 → "large project — this takes a while. Grab a coffee; bb keeps working in the
|
|
496
|
+
background". Measured verbatim on a 7-file project: *"Indexing 7 files — this is a small
|
|
497
|
+
project — a few minutes at most"* (it finished in ~2 s). The module docstring promises a
|
|
498
|
+
"a moment" band that does not exist in the code.
|
|
499
|
+
3. **Encouragement fires at thresholds that are absurd on small projects**, and one branch is
|
|
500
|
+
dead. `encouragementFor` (`:52-64`) announces "a quarter of the way in" at ≥25% — measured at
|
|
501
|
+
**1 of 7 files**. Its last two lines (`if (isBigProject && pct < 10) return null` / `return
|
|
502
|
+
null`) are equivalent, so the branch the comment describes is unreachable.
|
|
503
|
+
4. **The non-TTY milestone rate limit is dead code.** `renderNonTtyLine` (`:270-296`) computes
|
|
504
|
+
`dueByTime` from `nonTtyIntervalMs` but then returns whenever `bucket === lastNonTtyBucket`,
|
|
505
|
+
so wall-clock alone can never emit a line — the documented "AND by wall-clock, so neither a
|
|
506
|
+
very long run nor a stalled run floods the log" is not what the code does. This is also why a
|
|
507
|
+
250-second parent stall produced *no* output at all in the logs (§2 of the perf work).
|
|
508
|
+
5. **"Longer than we expect" fires on healthy runs.** `finish()` (`:298-313`) prints "That took
|
|
509
|
+
… — longer than we expect. If it felt like it hung, that is worth reporting" at ≥5 min
|
|
510
|
+
regardless of project size. Measured on the *healthy* 5,086-file run: "That took 7m 7s —
|
|
511
|
+
longer than we expect." The threshold should scale with the discovered file count (or the
|
|
512
|
+
wording should be neutral), otherwise the product tells users something is wrong precisely
|
|
513
|
+
when it is not.
|
|
514
|
+
6. **What is good and should be kept**: the "nothing to index — already up to date (N unchanged,
|
|
515
|
+
0 skipped, 0.2s)" path with its per-bucket breakdown; the `[index] <pct>% … elapsed=… eta=…`
|
|
516
|
+
non-TTY line shape; the one-line-per-5% behaviour; the fixed-width TTY bar; the honest
|
|
517
|
+
"Indexing N files — …" framing emitted once; and the summary's explicit
|
|
518
|
+
`0 newly parsed | 7 unchanged | 0 errors | 0 timeouts` accounting.
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 9. The flag surface
|
|
523
|
+
|
|
524
|
+
`bb index --help` exposes eight options: `--force`, `--no-setup`, `--skip-post-processing`,
|
|
525
|
+
`--post`, `--file <path>`, `--changed`, `--resume`, `--import <db>` (`cli/index.ts:4-11`).
|
|
526
|
+
|
|
527
|
+
1. **`--exclude` does not exist, though the indexer documents it.** `parallel-indexer.ts:1073`
|
|
528
|
+
reads *"Load CLI --exclude patterns (comma-separated globs)"* and `excludePatterns` is
|
|
529
|
+
threaded through — but no CLI option sets it, so `.bbignore` is the only user-facing exclusion
|
|
530
|
+
mechanism (and per §4.1 it cannot undo a directory-name skip).
|
|
531
|
+
2. **Concurrency is not overridable.** `ParallelIndexerConfig.concurrency` (`:125`) has no env or
|
|
532
|
+
CLI knob, so a user cannot cap workers on a loaded machine, ask for deterministic
|
|
533
|
+
single-worker runs, or raise it on a big host past the hard 12 cap.
|
|
534
|
+
3. **`--force` keeps the file** (documented at `cli/index.ts:167-170`, deliberately, so a live MCP
|
|
535
|
+
server's handle stays valid). Space is reclaimed instead by the wipe-path checkpoint + VACUUM
|
|
536
|
+
(`src/ast/index-space.ts`), which now decides on rows deleted rather than on `freelist_count`.
|
|
537
|
+
4. **No `--json` / `--quiet`**: the index command speaks human text only, so scripting it means
|
|
538
|
+
parsing prose. (`bb show`/`bb stats` cover some of this outside the index path.)
|
|
539
|
+
5. The remaining flags match their documented behaviour, including `--resume` reading the live
|
|
540
|
+
checkpoint and `--changed` costing seconds on an unchanged tree (verified: 0.2 s, "nothing to
|
|
541
|
+
index").
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 10. What I would fix, ranked
|
|
546
|
+
|
|
547
|
+
**Correctness first — small diffs, large consequences:**
|
|
548
|
+
|
|
549
|
+
1. `worker.on("error", …)` on pool workers: one line, prevents a whole-run death (§5.1).
|
|
550
|
+
2. Count a pre-`ready` exit toward `startupFailures`: prevents an infinite fork loop (§5.2).
|
|
551
|
+
3. **A failed store batch must fail the run.** Today a full disk rolls back batch after batch,
|
|
552
|
+
skips them, and still prints `✓ indexed N files…` (§6.3.1). Count the skips, persist
|
|
553
|
+
`partial`/`failed`, exit non-zero.
|
|
554
|
+
4. A rolled-back batch must not persist `status: "complete"` (§6.3.2) — `errorFiles` only counts
|
|
555
|
+
parse outcomes today.
|
|
556
|
+
5. **Resolve targets from memory instead of one sorted SELECT per edge.** `O(R × C log C)` sits on
|
|
557
|
+
the critical path in `dbFlushMs` (§7.1): the data is already in `nameToSymbol`, and the
|
|
558
|
+
`_i13_best_*` aggregates exist for exactly this ranking. This is the biggest algorithmic win
|
|
559
|
+
available, and it is the term the linear plan was written to remove from the other pass.
|
|
560
|
+
6. `largestIntermediate` must include `_i13_src` (§7.2): the gate's clock-free check divides a
|
|
561
|
+
number that omits a table of R rows by R.
|
|
562
|
+
7. Stop storing every vector twice — drop the JSON column's write, or the column (§3.1): ~660 MB
|
|
563
|
+
and a large share of embedding write time.
|
|
564
|
+
8. `shouldSkip` needs negation, and `bin`/`env`/`out`/`gen`/`deps` must leave the *segment* list
|
|
565
|
+
(§4.1): four real source trees in this repository are invisible today and unfixable by any
|
|
566
|
+
ignore file.
|
|
567
|
+
9. Add `.mts`/`.cts` to the language map and delete the dead `extensions` list (§4.2): 490 tracked
|
|
568
|
+
files in this repo.
|
|
569
|
+
10. `--skip-post-processing` must not persist `complete`, and the "consistency" line must count the
|
|
570
|
+
edges that carry an external identity but no target — 34,646 of them here (§7.7).
|
|
571
|
+
11. Taint must surface its 10,000-row truncation and must not swallow a query failure as an "ok"
|
|
572
|
+
phase outcome (§7.4).
|
|
573
|
+
|
|
574
|
+
**Then the honest surface:**
|
|
575
|
+
|
|
576
|
+
12. Phase-aware ETA: parse remainder + derived phases + the embedding queue, a windowed rate, and
|
|
577
|
+
"…" until there is signal (§8.1).
|
|
578
|
+
13. Message bands: a "seconds/a moment" band below ~50 files, and a "longer than we expect"
|
|
579
|
+
threshold that scales with the discovered file count (§8.2, §8.5).
|
|
580
|
+
14. Delete the dead branches in `encouragementFor` and `renderNonTtyLine`, or make them do what
|
|
581
|
+
their comments claim (§8.3, §8.4).
|
|
582
|
+
|
|
583
|
+
**Then space and throughput:**
|
|
584
|
+
|
|
585
|
+
15. Defer the 14 relationship indexes (549 MiB) and 10 symbol indexes (127 MB) during a full load
|
|
586
|
+
and build them once at the end, inside the run (§3.2, §6.2) — less write amplification per
|
|
587
|
+
insert, which is what `dbFlushMs` is made of.
|
|
588
|
+
16. Tune the bulk load: `temp_store = MEMORY` (the biggest intermediate and every per-edge sort are
|
|
589
|
+
file-backed today), a real page cache, and `journal_size_limit` (§6.4, §7.5).
|
|
590
|
+
17. Bound parent memory and add an IPC high-water mark (§3.3); the in-process fallback retains every
|
|
591
|
+
file result for the whole run, and `fileRelCounters` is never cleared (§7 memory).
|
|
592
|
+
18. Add a ≥1,000-file tier to `verify-scaling-linear.ts` and fix the intermediate accounting (§2,
|
|
593
|
+
§7.2) so the gate sees production scale.
|
|
594
|
+
19. Reclaim on ordinary runs, not only after `--force`, and move the end-of-run checkpoint after
|
|
595
|
+
the embedding drain (§6.6, §6.3.3) — 858 MiB (27%) of this index is currently never returned.
|
|
596
|
+
|
|
597
|
+
**Worth deciding deliberately (policy, not bugs):** whether `.d.ts`, fixtures and data formats stay
|
|
598
|
+
indexed; whether read-time exclusions and index-time discovery share one list (§4.11-4.12);
|
|
599
|
+
duplicate-content detection (§4.10); reporting the in-process 13k/14k clamp (§4.9); and refreshing
|
|
600
|
+
stale planner stats on incremental runs (§7.6).
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 11. Not covered by this review
|
|
605
|
+
|
|
606
|
+
- **Not measured:** the split of the 404 s store phase between the per-edge SELECT, 17-way index
|
|
607
|
+
maintenance and the per-file DELETEs. Two reviewers bounded it (their additive estimate exceeds
|
|
608
|
+
the phase, so the proxy over-counts), but no run isolated the terms — that needs instrumentation,
|
|
609
|
+
which is a write.
|
|
610
|
+
- **Not observed in production:** §5.1's EPIPE ordering and §5.2's crash-before-`ready` loop. Both
|
|
611
|
+
are demonstrated at the Node/code level with real child processes, not caught in a run.
|
|
612
|
+
- **Not measured:** parent peak RSS per 1,000 retained symbols (the mechanism is certain from the
|
|
613
|
+
code; the constant is not), and the same for the in-process fallback's retained results.
|
|
614
|
+
- **Not reproduced:** the documented no-stats blowups (260× / 600× / 200–400×). The reviewer
|
|
615
|
+
reproduced the plan *changes* on a schema-identical A/B, not the blowups, whose preconditions are
|
|
616
|
+
data-skew dependent.
|
|
617
|
+
- **Not exercised:** the embedding drainer and semantic search on a genuinely mixed-model index
|
|
618
|
+
(read from code plus in-repo measurements); Bun's bundled SQLite (only the Node better-sqlite3
|
|
619
|
+
3.49.2 path was probed, so `TEMP_STORE` may differ there).
|
|
620
|
+
- **Out of scope:** LSP/MCP read paths beyond what the discovery pass touched, every language
|
|
621
|
+
analyzer's own parse cost, and why 124,711 edges (25.1%) resolve to no symbol at all — that is
|
|
622
|
+
analyzer work, not indexer work.
|
|
623
|
+
|
|
624
|
+
## Verification status of this document
|
|
625
|
+
|
|
626
|
+
| Section | How it was established |
|
|
627
|
+
|---|---|
|
|
628
|
+
| §1, §2, §3 | Measured on this machine: `brain_index_status.performance_stats`, `dbstat`, `PRAGMA`/`compile_options`, and the committed run logs |
|
|
629
|
+
| §4 | A review pass that **executed the real `discoverIndexableFiles`** against synthetic fixtures, plus my own audit of the index against `git ls-files`; I re-read the CRITICAL finding, the no-op logger and the 256 KB truncation myself |
|
|
630
|
+
| §5 | A review pass that reproduced the IPC orderings with real child processes; I re-verified both HIGH findings in the source (the only `error` listener is the fork probe's; `clearStartupTimer` is first in the exit handler) |
|
|
631
|
+
| §6 | A review pass that ran `EXPLAIN QUERY PLAN` and per-candidate timings on a schema-identical copy and `dbstat`-measured the real index; I re-read its two headline claims (the per-edge SELECT inside the store loop; `largestIntermediate` excluding `_i13_src`) |
|
|
632
|
+
| §7 | A review pass with real-database counts/distributions and synthetic timings; I re-read the `largestIntermediate` exclusion and the unguarded per-edge resolve |
|
|
633
|
+
| §8, §9 | Read and measured by me, including verbatim CLI runs on 7-file, 205-file and 5,086-file projects |
|
|
634
|
+
| §3.1, §6.4, §7.8 | Cross-checked against the reviewers' independent `dbstat`/`PRAGMA` measurements, which agreed byte-for-byte apart from one corrected object count |
|