@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,1119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bbProjectsDir
|
|
3
|
+
} from "./chunk-2BSKD7O3.js";
|
|
4
|
+
import {
|
|
5
|
+
brain_sql_exports
|
|
6
|
+
} from "./chunk-ROMM6KMC.js";
|
|
7
|
+
|
|
8
|
+
// src/storage/db.node.ts
|
|
9
|
+
import Database from "better-sqlite3";
|
|
10
|
+
function openDatabase(path2, options = {}) {
|
|
11
|
+
const db = new Database(path2, { readonly: options.readonly ?? false });
|
|
12
|
+
if (!options.readonly) {
|
|
13
|
+
db.pragma("journal_mode = WAL");
|
|
14
|
+
db.pragma("busy_timeout = 5000");
|
|
15
|
+
db.pragma("synchronous = NORMAL");
|
|
16
|
+
const tempStore = String(process.env.BB_SQLITE_TEMP_STORE ?? "").toUpperCase();
|
|
17
|
+
if (tempStore === "MEMORY" || tempStore === "FILE" || tempStore === "DEFAULT") db.pragma(`temp_store = ${tempStore}`);
|
|
18
|
+
else if (tempStore === "0" || tempStore === "1" || tempStore === "2") db.pragma(`temp_store = ${tempStore}`);
|
|
19
|
+
const cacheMb = Number(process.env.BB_SQLITE_CACHE_MB ?? 0);
|
|
20
|
+
if (Number.isInteger(cacheMb) && cacheMb >= 1 && cacheMb <= 4096) db.pragma(`cache_size = -${cacheMb * 1024}`);
|
|
21
|
+
db.pragma(process.env.BB_FK_ENFORCE === "0" ? "foreign_keys = OFF" : "foreign_keys = ON");
|
|
22
|
+
} else {
|
|
23
|
+
db.pragma(process.env.BB_FK_ENFORCE === "0" ? "foreign_keys = OFF" : "foreign_keys = ON");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
native: db,
|
|
27
|
+
prepare(sql) {
|
|
28
|
+
const stmt = db.prepare(sql);
|
|
29
|
+
return {
|
|
30
|
+
get: (...params) => stmt.get(...params),
|
|
31
|
+
all: (...params) => stmt.all(...params),
|
|
32
|
+
run: (...params) => {
|
|
33
|
+
const r = stmt.run(...params);
|
|
34
|
+
return { changes: Number(r.changes ?? 0), lastInsertRowid: Number(r.lastInsertRowid ?? 0) };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
exec(sql) {
|
|
39
|
+
db.exec(sql);
|
|
40
|
+
},
|
|
41
|
+
close() {
|
|
42
|
+
db.close();
|
|
43
|
+
},
|
|
44
|
+
transaction(fn) {
|
|
45
|
+
const run = db.transaction(fn);
|
|
46
|
+
return (...args) => run(...args);
|
|
47
|
+
},
|
|
48
|
+
run(sql, ...params) {
|
|
49
|
+
const r = db.prepare(sql).run(...params);
|
|
50
|
+
return { changes: Number(r.changes ?? 0), lastInsertRowid: Number(r.lastInsertRowid ?? 0) };
|
|
51
|
+
},
|
|
52
|
+
readonly: options.readonly ?? false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/brain/database.ts
|
|
57
|
+
import path from "path";
|
|
58
|
+
import { mkdirSync, existsSync } from "fs";
|
|
59
|
+
|
|
60
|
+
// src/brain/schema-ddl.ts
|
|
61
|
+
var DDL = `
|
|
62
|
+
CREATE TABLE IF NOT EXISTS brain_schema_version (version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL, migration_name TEXT NOT NULL);
|
|
63
|
+
-- ================================================================
|
|
64
|
+
-- UNIVERSAL PROJECT BRAIN DATABASE FOUNDATION
|
|
65
|
+
-- Cross-language intelligence system
|
|
66
|
+
-- ================================================================
|
|
67
|
+
|
|
68
|
+
-- Core Files Table
|
|
69
|
+
CREATE TABLE IF NOT EXISTS brain_file (
|
|
70
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
71
|
+
project_id TEXT NOT NULL,
|
|
72
|
+
path TEXT NOT NULL,
|
|
73
|
+
relative_path TEXT NOT NULL,
|
|
74
|
+
file_hash TEXT NOT NULL,
|
|
75
|
+
size_bytes INTEGER,
|
|
76
|
+
line_count INTEGER,
|
|
77
|
+
language TEXT,
|
|
78
|
+
last_modified REAL,
|
|
79
|
+
indexed_at TEXT,
|
|
80
|
+
layer TEXT,
|
|
81
|
+
is_test_file INTEGER,
|
|
82
|
+
is_config_file INTEGER,
|
|
83
|
+
is_documentation INTEGER,
|
|
84
|
+
has_syntax_errors INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
error_report TEXT,
|
|
86
|
+
framework_type TEXT,
|
|
87
|
+
-- The analyzer's FileMetadata as JSON: patterns, detectedFrameworks,
|
|
88
|
+
-- frameworkPatterns, architecturalInsights, complexity, cyclomaticComplexity,
|
|
89
|
+
-- codeQuality. The framework_type column above is the ONE field promoted
|
|
90
|
+
-- to its own column;
|
|
91
|
+
-- everything else the analyzer computed had nowhere to go, so a correct
|
|
92
|
+
-- analyzer's pattern evidence was discarded on the way into the index.
|
|
93
|
+
-- This is the same shape brain_relationship.metadata already uses.
|
|
94
|
+
metadata TEXT,
|
|
95
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
96
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
-- Universal Symbols Table
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS brain_symbol (
|
|
102
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
103
|
+
project_id TEXT NOT NULL,
|
|
104
|
+
file_id TEXT NOT NULL,
|
|
105
|
+
name TEXT NOT NULL,
|
|
106
|
+
qualified_name TEXT,
|
|
107
|
+
symbol_type TEXT NOT NULL,
|
|
108
|
+
line_start INTEGER,
|
|
109
|
+
line_end INTEGER,
|
|
110
|
+
byte_start INTEGER,
|
|
111
|
+
byte_end INTEGER,
|
|
112
|
+
return_type TEXT,
|
|
113
|
+
parameters TEXT,
|
|
114
|
+
docstring TEXT,
|
|
115
|
+
-- The symbol's own source text, bounded. This is what makes a word
|
|
116
|
+
-- that appears ONLY inside a function reachable by a text query.
|
|
117
|
+
-- Measured on real corpora before it existed: content search selected
|
|
118
|
+
-- name/qualified_name/docstring and there was no body column at all, so
|
|
119
|
+
-- a body-only word was unreachable by BOTH retrieval paths \u2014 27% of
|
|
120
|
+
-- body-only words on rake, 38% on ripgrep. The body WAS indexed, but
|
|
121
|
+
-- only as a 50-dimensional hash, which cannot be queried by word.
|
|
122
|
+
-- Bounded because a symbol can be very large (a 25 599-character body
|
|
123
|
+
-- was measured on rake) and this table is scanned by content search.
|
|
124
|
+
source_text TEXT,
|
|
125
|
+
visibility TEXT,
|
|
126
|
+
is_async INTEGER,
|
|
127
|
+
is_static INTEGER,
|
|
128
|
+
is_abstract INTEGER,
|
|
129
|
+
is_override INTEGER,
|
|
130
|
+
is_getter INTEGER,
|
|
131
|
+
is_setter INTEGER,
|
|
132
|
+
complexity INTEGER,
|
|
133
|
+
owner_symbol_id TEXT,
|
|
134
|
+
scope_kind TEXT,
|
|
135
|
+
-- #612: 1 = the analyzer published this symbol as a DECLARATION
|
|
136
|
+
-- (a c prototype, a cpp in-class/field-declaration or header
|
|
137
|
+
-- prototype, a rust function_signature_item, a body-less zig
|
|
138
|
+
-- function declaration), NULL = a definition or a language that does
|
|
139
|
+
-- not distinguish. Name resolution reads this to prefer the
|
|
140
|
+
-- definition when a declaration and its definition share a name.
|
|
141
|
+
is_declaration INTEGER,
|
|
142
|
+
annotations TEXT,
|
|
143
|
+
generic_parameters TEXT,
|
|
144
|
+
qualified_calls TEXT,
|
|
145
|
+
-- M60/SDJ-8: the type hierarchy each symbol extends/implements, as a JSON
|
|
146
|
+
-- array of names (["Base","Mixin"]). Present because the overrides label
|
|
147
|
+
-- could not be resolved cross-file at all without it: brain_symbol held no
|
|
148
|
+
-- heritage data, so the post-index pass could only resolve targets BY NAME
|
|
149
|
+
-- which resolves the wrong parent perfectly happily. Measured on scala3:
|
|
150
|
+
-- 45.5% of low-confidence override rows pointed at a supertype that does
|
|
151
|
+
-- NOT declare the overridden method, and 0 of 677 were fixable from one
|
|
152
|
+
-- file. NULL = "this symbol declares no supertypes", which is also what
|
|
153
|
+
-- every pre-SDJ-8 row means, so an un-migrated database degrades to the
|
|
154
|
+
-- previous behaviour rather than mis-resolving.
|
|
155
|
+
super_types TEXT,
|
|
156
|
+
api_contract_path TEXT,
|
|
157
|
+
gravity_score REAL DEFAULT 0,
|
|
158
|
+
layer TEXT DEFAULT "core",
|
|
159
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
160
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
161
|
+
FOREIGN KEY (file_id) REFERENCES brain_file(id) ON DELETE CASCADE
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
-- Cross-Language Relationships Table
|
|
165
|
+
CREATE TABLE IF NOT EXISTS brain_relationship (
|
|
166
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
167
|
+
project_id TEXT NOT NULL,
|
|
168
|
+
source_symbol_id TEXT,
|
|
169
|
+
target_symbol_id TEXT,
|
|
170
|
+
source_external TEXT,
|
|
171
|
+
target_external TEXT,
|
|
172
|
+
-- The RESOLUTION BASIS of the target \u2014 see ensureRelationshipTargetBasisColumn for why this is
|
|
173
|
+
-- a VIRTUAL GENERATED column rather than a writer-maintained one.
|
|
174
|
+
target_basis TEXT GENERATED ALWAYS AS (
|
|
175
|
+
CASE
|
|
176
|
+
WHEN target_symbol_id IS NOT NULL THEN 'symbol'
|
|
177
|
+
WHEN metadata IS NOT NULL AND json_extract(metadata, '$.isExternalTarget') = 1 THEN 'external'
|
|
178
|
+
WHEN target_external IS NOT NULL THEN 'unresolved'
|
|
179
|
+
ELSE 'unresolved'
|
|
180
|
+
END
|
|
181
|
+
) VIRTUAL,
|
|
182
|
+
-- The EVIDENCE CLASS of the edge, extracted once (same generated-column
|
|
183
|
+
-- rule as target_basis): pattern rules read this per row for every
|
|
184
|
+
-- relationship in the project, and reading the wide metadata TEXT at
|
|
185
|
+
-- that rate made a 3.6 GB default scan 100+ s. A covering index over the
|
|
186
|
+
-- narrow computed column lets the scan run inside the index.
|
|
187
|
+
evidence_class TEXT GENERATED ALWAYS AS (json_extract(metadata, '$.evidenceClass')) VIRTUAL,
|
|
188
|
+
relationship_type TEXT NOT NULL,
|
|
189
|
+
confidence REAL NOT NULL,
|
|
190
|
+
context TEXT,
|
|
191
|
+
line_number INTEGER,
|
|
192
|
+
is_cross_language INTEGER,
|
|
193
|
+
bridge_mechanism TEXT,
|
|
194
|
+
is_cross_file INTEGER NOT NULL DEFAULT 0,
|
|
195
|
+
metadata TEXT,
|
|
196
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
197
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
198
|
+
FOREIGN KEY (source_symbol_id) REFERENCES brain_symbol(id) ON DELETE CASCADE,
|
|
199
|
+
FOREIGN KEY (target_symbol_id) REFERENCES brain_symbol(id) ON DELETE SET NULL
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
-- Imports/Dependencies Table
|
|
203
|
+
CREATE TABLE IF NOT EXISTS brain_import (
|
|
204
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
205
|
+
project_id TEXT NOT NULL,
|
|
206
|
+
file_id TEXT NOT NULL,
|
|
207
|
+
imported_name TEXT NOT NULL,
|
|
208
|
+
imported_as TEXT,
|
|
209
|
+
import_path TEXT NOT NULL,
|
|
210
|
+
import_type TEXT NOT NULL,
|
|
211
|
+
is_external INTEGER,
|
|
212
|
+
package_name TEXT,
|
|
213
|
+
line_number INTEGER,
|
|
214
|
+
is_dynamic INTEGER,
|
|
215
|
+
resolved_symbol_id TEXT,
|
|
216
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
217
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
218
|
+
FOREIGN KEY (file_id) REFERENCES brain_file(id) ON DELETE CASCADE
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
-- Pattern Intelligence Table
|
|
222
|
+
|
|
223
|
+
-- Query Cache Table
|
|
224
|
+
CREATE TABLE IF NOT EXISTS brain_query_cache (
|
|
225
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
226
|
+
project_id TEXT NOT NULL,
|
|
227
|
+
query_type TEXT NOT NULL,
|
|
228
|
+
query_hash TEXT NOT NULL,
|
|
229
|
+
query_params TEXT,
|
|
230
|
+
result_data TEXT,
|
|
231
|
+
confidence REAL NOT NULL,
|
|
232
|
+
computation_time_ms INTEGER,
|
|
233
|
+
cache_expires_at INTEGER,
|
|
234
|
+
hit_count INTEGER DEFAULT 0 NOT NULL,
|
|
235
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
236
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
-- Index Status Tracking
|
|
240
|
+
CREATE TABLE IF NOT EXISTS brain_index_status (
|
|
241
|
+
project_id TEXT PRIMARY KEY NOT NULL,
|
|
242
|
+
status TEXT,
|
|
243
|
+
last_full_index INTEGER,
|
|
244
|
+
last_incremental_index INTEGER,
|
|
245
|
+
total_files INTEGER DEFAULT 0 NOT NULL,
|
|
246
|
+
indexed_files INTEGER DEFAULT 0 NOT NULL,
|
|
247
|
+
total_symbols INTEGER DEFAULT 0 NOT NULL,
|
|
248
|
+
total_relationships INTEGER DEFAULT 0 NOT NULL,
|
|
249
|
+
indexing_version TEXT NOT NULL,
|
|
250
|
+
languages_detected TEXT,
|
|
251
|
+
frameworks_detected TEXT,
|
|
252
|
+
indexing_errors TEXT,
|
|
253
|
+
performance_stats TEXT,
|
|
254
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
255
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
-- ================================================================
|
|
259
|
+
-- PERFORMANCE INDEXES - Optimized for graph queries
|
|
260
|
+
-- ================================================================
|
|
261
|
+
|
|
262
|
+
CREATE INDEX IF NOT EXISTS brain_file_project_idx ON brain_file(project_id);
|
|
263
|
+
CREATE INDEX IF NOT EXISTS brain_file_path_idx ON brain_file(path);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS brain_file_language_idx ON brain_file(language);
|
|
265
|
+
CREATE INDEX IF NOT EXISTS brain_file_hash_idx ON brain_file(file_hash);
|
|
266
|
+
|
|
267
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_project_idx ON brain_symbol(project_id);
|
|
268
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_file_idx ON brain_symbol(file_id);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_name_idx ON brain_symbol(name);
|
|
270
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_type_idx ON brain_symbol(symbol_type);
|
|
271
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_qualified_idx ON brain_symbol(qualified_name);
|
|
272
|
+
|
|
273
|
+
CREATE INDEX IF NOT EXISTS brain_rel_project_idx ON brain_relationship(project_id);
|
|
274
|
+
CREATE INDEX IF NOT EXISTS brain_rel_source_idx ON brain_relationship(source_symbol_id);
|
|
275
|
+
CREATE INDEX IF NOT EXISTS brain_rel_target_idx ON brain_relationship(target_symbol_id);
|
|
276
|
+
CREATE INDEX IF NOT EXISTS brain_rel_type_idx ON brain_relationship(relationship_type);
|
|
277
|
+
CREATE INDEX IF NOT EXISTS brain_rel_source_type_idx ON brain_relationship(source_symbol_id, relationship_type);
|
|
278
|
+
CREATE INDEX IF NOT EXISTS brain_rel_proj_type_target_idx ON brain_relationship(project_id, relationship_type, target_symbol_id);
|
|
279
|
+
CREATE INDEX IF NOT EXISTS brain_rel_confidence_idx ON brain_relationship(confidence);
|
|
280
|
+
CREATE INDEX IF NOT EXISTS brain_rel_cross_file_idx ON brain_relationship(is_cross_file);
|
|
281
|
+
CREATE INDEX IF NOT EXISTS brain_rel_target_ext_idx ON brain_relationship(target_external);
|
|
282
|
+
|
|
283
|
+
CREATE INDEX IF NOT EXISTS brain_import_project_idx ON brain_import(project_id);
|
|
284
|
+
CREATE INDEX IF NOT EXISTS brain_import_file_idx ON brain_import(file_id);
|
|
285
|
+
CREATE INDEX IF NOT EXISTS brain_import_path_idx ON brain_import(import_path);
|
|
286
|
+
|
|
287
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_owner_idx ON brain_symbol(owner_symbol_id);
|
|
288
|
+
-- Owner-cascade seeks carry the project filter, so the single-column owner index
|
|
289
|
+
-- cannot serve the range: (project_id, owner_symbol_id) lets the walk seek the
|
|
290
|
+
-- owner's members instead of scanning every symbol in the project per node.
|
|
291
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_project_owner_idx ON brain_symbol(project_id, owner_symbol_id);
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
-- Initialize project brain status
|
|
295
|
+
INSERT OR IGNORE INTO brain_index_status (
|
|
296
|
+
project_id,
|
|
297
|
+
indexing_version,
|
|
298
|
+
languages_detected,
|
|
299
|
+
frameworks_detected,
|
|
300
|
+
indexing_errors,
|
|
301
|
+
performance_stats,
|
|
302
|
+
time_created,
|
|
303
|
+
time_updated
|
|
304
|
+
) VALUES (
|
|
305
|
+
?,
|
|
306
|
+
'v1.0.0',
|
|
307
|
+
'[]',
|
|
308
|
+
'[]',
|
|
309
|
+
'[]',
|
|
310
|
+
'{}',
|
|
311
|
+
strftime('%s','now') * 1000,
|
|
312
|
+
strftime('%s','now') * 1000
|
|
313
|
+
);
|
|
314
|
+
CREATE TABLE IF NOT EXISTS brain_project_health (
|
|
315
|
+
project_id TEXT PRIMARY KEY NOT NULL,
|
|
316
|
+
complexity_score INTEGER NOT NULL DEFAULT 0,
|
|
317
|
+
maintainability_score INTEGER NOT NULL DEFAULT 0,
|
|
318
|
+
technical_debt_score INTEGER NOT NULL DEFAULT 0,
|
|
319
|
+
code_quality_score INTEGER NOT NULL DEFAULT 0,
|
|
320
|
+
language_distribution TEXT,
|
|
321
|
+
service_metrics TEXT,
|
|
322
|
+
dependency_metrics TEXT,
|
|
323
|
+
circular_dependencies TEXT,
|
|
324
|
+
contract_drift TEXT,
|
|
325
|
+
performance_bottlenecks TEXT,
|
|
326
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
327
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
CREATE TABLE IF NOT EXISTS brain_taint_flow (
|
|
331
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
332
|
+
project_id TEXT NOT NULL,
|
|
333
|
+
source_symbol_id TEXT,
|
|
334
|
+
sink_symbol_id TEXT,
|
|
335
|
+
source_type TEXT NOT NULL,
|
|
336
|
+
sink_type TEXT NOT NULL,
|
|
337
|
+
vulnerability_type TEXT NOT NULL,
|
|
338
|
+
severity TEXT NOT NULL,
|
|
339
|
+
is_mitigated INTEGER NOT NULL DEFAULT 0,
|
|
340
|
+
flow_path TEXT,
|
|
341
|
+
cross_language_boundaries INTEGER NOT NULL DEFAULT 0,
|
|
342
|
+
mitigation_steps TEXT,
|
|
343
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
344
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
345
|
+
);
|
|
346
|
+
CREATE INDEX IF NOT EXISTS brain_taint_project_idx ON brain_taint_flow(project_id);
|
|
347
|
+
CREATE INDEX IF NOT EXISTS brain_taint_source_idx ON brain_taint_flow(source_symbol_id);
|
|
348
|
+
CREATE INDEX IF NOT EXISTS brain_taint_sink_idx ON brain_taint_flow(sink_symbol_id);
|
|
349
|
+
CREATE INDEX IF NOT EXISTS brain_taint_vuln_idx ON brain_taint_flow(vulnerability_type);
|
|
350
|
+
CREATE INDEX IF NOT EXISTS brain_taint_severity_idx ON brain_taint_flow(severity);
|
|
351
|
+
|
|
352
|
+
-- ================================================================
|
|
353
|
+
-- COMPOUND & MISSING INDEXES - aligned with the drizzle schema
|
|
354
|
+
-- ================================================================
|
|
355
|
+
|
|
356
|
+
CREATE INDEX IF NOT EXISTS brain_file_proj_path_idx ON brain_file(project_id, path);
|
|
357
|
+
CREATE INDEX IF NOT EXISTS brain_file_proj_hash_idx ON brain_file(project_id, file_hash);
|
|
358
|
+
CREATE INDEX IF NOT EXISTS brain_file_syntax_errors_idx ON brain_file(has_syntax_errors);
|
|
359
|
+
|
|
360
|
+
CREATE INDEX IF NOT EXISTS brain_sym_proj_file_idx ON brain_symbol(project_id, file_id); CREATE INDEX IF NOT EXISTS brain_sym_proj_name_idx ON brain_symbol(project_id, name);
|
|
361
|
+
-- Case-insensitive name index: LOWER(name) = LOWER(?) wraps the column and
|
|
362
|
+
-- defeats the BINARY index (full scan on 1M symbols, M19.Q1 measured ~1.3s
|
|
363
|
+
-- with the join). SQLite's NOCASE collation folds ASCII exactly like its
|
|
364
|
+
-- LOWER(), so name = ? COLLATE NOCASE / name LIKE ? COLLATE NOCASE are
|
|
365
|
+
-- semantically identical to the old LOWER() forms but index-accelerated.
|
|
366
|
+
CREATE INDEX IF NOT EXISTS brain_sym_proj_name_nocase_idx ON brain_symbol(project_id, name COLLATE NOCASE);
|
|
367
|
+
CREATE INDEX IF NOT EXISTS brain_sym_proj_type_idx ON brain_symbol(project_id, symbol_type);
|
|
368
|
+
CREATE INDEX IF NOT EXISTS brain_sym_proj_name_type_idx ON brain_symbol(project_id, name, symbol_type);
|
|
369
|
+
|
|
370
|
+
-- MEASURED (bb_visualize on cargo: 2,071 files, 40,595 symbols, 164,767
|
|
371
|
+
-- relationships): bb_visualize's project scope orders symbols by gravity_score to
|
|
372
|
+
-- pick the project's busiest core, and SQLite had no index that could supply that
|
|
373
|
+
-- order, so it materialised and sorted ALL 40,595 rows into a temp B-tree on every
|
|
374
|
+
-- single call. Interleaved A/B, min of 6: 570.6 ms without this index, 2.8 ms with
|
|
375
|
+
-- it -- 139x -- and the plan changes from
|
|
376
|
+
-- SCAN f | SEARCH s ... | USE TEMP B-TREE FOR ORDER BY
|
|
377
|
+
-- to
|
|
378
|
+
-- SEARCH s USING INDEX brain_symbol_gravity_idx (project_id=?)
|
|
379
|
+
-- which is a noise-free fact: the sort is gone, not merely faster.
|
|
380
|
+
--
|
|
381
|
+
-- line_start is the tie-breaker so the order is total and a render is reproducible.
|
|
382
|
+
--
|
|
383
|
+
-- Deliberately NOT added, having measured both:
|
|
384
|
+
-- * brain_relationship(project_id, source_symbol_id, target_symbol_id) -- 4.4 s to
|
|
385
|
+
-- build on cargo, and it made the joined edge fetch 38x SLOWER (18.8 ms ->
|
|
386
|
+
-- 714.4 ms), because the existing brain_rel_proj_source_idx already answers that
|
|
387
|
+
-- query better. An index that costs four seconds of every index run to lose a
|
|
388
|
+
-- benchmark is not an optimisation.
|
|
389
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_gravity_idx ON brain_symbol(project_id, gravity_score DESC, line_start);
|
|
390
|
+
|
|
391
|
+
CREATE INDEX IF NOT EXISTS brain_rel_proj_target_idx ON brain_relationship(project_id, target_symbol_id);
|
|
392
|
+
CREATE INDEX IF NOT EXISTS brain_rel_proj_source_idx ON brain_relationship(project_id, source_symbol_id);
|
|
393
|
+
CREATE INDEX IF NOT EXISTS brain_rel_proj_type_idx ON brain_relationship(project_id, relationship_type);
|
|
394
|
+
CREATE INDEX IF NOT EXISTS brain_rel_target_type_idx ON brain_relationship(target_symbol_id, relationship_type);
|
|
395
|
+
CREATE INDEX IF NOT EXISTS brain_rel_cross_lang_idx ON brain_relationship(is_cross_language);
|
|
396
|
+
-- M29.R3 (giant-repo, 59k files): hover's cross-language probes filter on
|
|
397
|
+
-- is_cross_language = 1 AND target/source_symbol_id = ?. The single-column
|
|
398
|
+
-- indexes leave SQLite scanning; the composite (symbol_id, is_cross_language)
|
|
399
|
+
-- form answers both probes from the index (measured 477ms -> <1ms at scale).
|
|
400
|
+
CREATE INDEX IF NOT EXISTS brain_rel_target_cross_idx ON brain_relationship(target_symbol_id, is_cross_language);
|
|
401
|
+
CREATE INDEX IF NOT EXISTS brain_rel_source_cross_idx ON brain_relationship(source_symbol_id, is_cross_language);
|
|
402
|
+
-- Pattern engines scan every relationship of a project, group by target, and
|
|
403
|
+
-- compute a per-row evidence gate. This index carries all four columns those
|
|
404
|
+
-- queries read (target/source ids, confidence, evidence class), so the scan never
|
|
405
|
+
-- touches the 3.6 GB table heap or the wide metadata column.
|
|
406
|
+
CREATE INDEX IF NOT EXISTS brain_rel_pattern_cover_idx ON brain_relationship(project_id, relationship_type, target_symbol_id, source_symbol_id, confidence, evidence_class, target_external);
|
|
407
|
+
-- The reported-type scan reads symbol columns for every symbol of a set of types;
|
|
408
|
+
-- (project_id, symbol_type) narrows it without a full symbol-table scan.
|
|
409
|
+
CREATE INDEX IF NOT EXISTS brain_symbol_type_cover_idx ON brain_symbol(project_id, symbol_type);
|
|
410
|
+
|
|
411
|
+
CREATE INDEX IF NOT EXISTS brain_import_name_idx ON brain_import(imported_name);
|
|
412
|
+
CREATE INDEX IF NOT EXISTS brain_import_external_idx ON brain_import(is_external);
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
CREATE INDEX IF NOT EXISTS brain_cache_project_idx ON brain_query_cache(project_id);
|
|
416
|
+
CREATE INDEX IF NOT EXISTS brain_cache_query_type_idx ON brain_query_cache(query_type);
|
|
417
|
+
CREATE INDEX IF NOT EXISTS brain_cache_hash_idx ON brain_query_cache(query_hash);
|
|
418
|
+
|
|
419
|
+
CREATE TABLE IF NOT EXISTS brain_semantic_embedding (
|
|
420
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
421
|
+
project_id TEXT NOT NULL,
|
|
422
|
+
entity_id TEXT NOT NULL,
|
|
423
|
+
entity_type TEXT NOT NULL,
|
|
424
|
+
embedding_model TEXT NOT NULL,
|
|
425
|
+
-- DENSE vectors live in embedding_blob (little-endian float32); this
|
|
426
|
+
-- column holds an EMPTY STRING for those rows. It still carries the
|
|
427
|
+
-- JSON form for SPARSE token lists (tfidf-v1) and for LEGACY dense
|
|
428
|
+
-- rows written before the 2026-11-01 contract, whose blob is NULL.
|
|
429
|
+
-- Why: storing both duplicated 34% of an 84.5 MB index as JSON digits
|
|
430
|
+
-- (measured on apollo-server: 28.9 MB JSON beside 9.15 MB float32).
|
|
431
|
+
-- Read through readRowVector() (src/brain/semantic/vector-binary.ts).
|
|
432
|
+
embedding_vector TEXT NOT NULL,
|
|
433
|
+
embedding_blob BLOB,
|
|
434
|
+
vector_dimension INTEGER NOT NULL,
|
|
435
|
+
content_hash TEXT NOT NULL,
|
|
436
|
+
semantic_context TEXT,
|
|
437
|
+
similarity_threshold REAL DEFAULT 0.7,
|
|
438
|
+
last_updated INTEGER NOT NULL,
|
|
439
|
+
time_created INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
440
|
+
time_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
441
|
+
);
|
|
442
|
+
CREATE INDEX IF NOT EXISTS brain_embed_project_idx ON brain_semantic_embedding(project_id);
|
|
443
|
+
CREATE INDEX IF NOT EXISTS brain_embed_entity_idx ON brain_semantic_embedding(entity_id);
|
|
444
|
+
CREATE INDEX IF NOT EXISTS brain_embed_type_idx ON brain_semantic_embedding(entity_type);
|
|
445
|
+
CREATE INDEX IF NOT EXISTS brain_embed_model_idx ON brain_semantic_embedding(embedding_model);
|
|
446
|
+
CREATE INDEX IF NOT EXISTS brain_embed_hash_idx ON brain_semantic_embedding(content_hash);
|
|
447
|
+
-- M52.P1 (#721): covering index for the bb_health aggregates (models/dims/
|
|
448
|
+
-- entities). Without it SQLite reads full ~8KB vector BLOB rows to reach four
|
|
449
|
+
-- metadata columns \u2014 measured 12.7s per bb_health call at 158k x 2048-dim.
|
|
450
|
+
CREATE INDEX IF NOT EXISTS brain_embed_health_idx ON brain_semantic_embedding(project_id, embedding_model, entity_id, vector_dimension);
|
|
451
|
+
|
|
452
|
+
`;
|
|
453
|
+
var SCHEMA_VERSION = 1;
|
|
454
|
+
var SCHEMA_MIGRATION_NAME = "m13-universal-brain";
|
|
455
|
+
var ANNOTATION_DDL = `
|
|
456
|
+
CREATE TABLE IF NOT EXISTS brain_annotation (
|
|
457
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
458
|
+
project_id TEXT NOT NULL,
|
|
459
|
+
symbol_name TEXT NOT NULL,
|
|
460
|
+
symbol_path TEXT,
|
|
461
|
+
author_class TEXT NOT NULL,
|
|
462
|
+
tool_identity TEXT NOT NULL,
|
|
463
|
+
body TEXT NOT NULL,
|
|
464
|
+
time_created INTEGER NOT NULL,
|
|
465
|
+
time_updated INTEGER NOT NULL
|
|
466
|
+
);
|
|
467
|
+
CREATE INDEX IF NOT EXISTS brain_annotation_project_symbol_idx ON brain_annotation(project_id, symbol_name);
|
|
468
|
+
CREATE INDEX IF NOT EXISTS brain_annotation_project_idx ON brain_annotation(project_id);
|
|
469
|
+
-- Location identity in the schema, not in an encoded id string. COALESCE
|
|
470
|
+
-- is load-bearing: symbol_path is nullable and SQLite counts NULLs as
|
|
471
|
+
-- distinct in a UNIQUE index, so a path-less note must not escape the
|
|
472
|
+
-- constraint. See src/brain/annotation-store.ts for the measured defect
|
|
473
|
+
-- this replaces.
|
|
474
|
+
CREATE UNIQUE INDEX IF NOT EXISTS brain_annotation_location_idx
|
|
475
|
+
ON brain_annotation(project_id, symbol_name, author_class, COALESCE(symbol_path, ''));
|
|
476
|
+
`;
|
|
477
|
+
var ANNOTATION_AUTHOR_CLASS_TRIGGER_DDL = `
|
|
478
|
+
CREATE TRIGGER IF NOT EXISTS brain_annotation_author_class_guard
|
|
479
|
+
BEFORE INSERT ON brain_annotation
|
|
480
|
+
WHEN NEW.author_class IS NULL OR NEW.author_class NOT IN ('agent', 'human')
|
|
481
|
+
BEGIN
|
|
482
|
+
SELECT RAISE(ABORT, 'brain_annotation.author_class must be ''agent'' or ''human''');
|
|
483
|
+
END;
|
|
484
|
+
CREATE TRIGGER IF NOT EXISTS brain_annotation_author_class_guard_update
|
|
485
|
+
BEFORE UPDATE OF author_class ON brain_annotation
|
|
486
|
+
WHEN NEW.author_class IS NULL OR NEW.author_class NOT IN ('agent', 'human')
|
|
487
|
+
BEGIN
|
|
488
|
+
SELECT RAISE(ABORT, 'brain_annotation.author_class must be ''agent'' or ''human''');
|
|
489
|
+
END;
|
|
490
|
+
`;
|
|
491
|
+
function ensureAnnotationTable(raw) {
|
|
492
|
+
try {
|
|
493
|
+
raw.exec(ANNOTATION_DDL);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
const wantsGuard = ANNOTATION_AUTHOR_CLASS_TRIGGER_DDL;
|
|
497
|
+
const updateMarker = "CREATE TRIGGER IF NOT EXISTS brain_annotation_author_class_guard_update";
|
|
498
|
+
const updateIndex = wantsGuard.indexOf(updateMarker);
|
|
499
|
+
const wantedStatements = [
|
|
500
|
+
{ name: "brain_annotation_author_class_guard", sql: wantsGuard.slice(0, updateIndex) },
|
|
501
|
+
{ name: "brain_annotation_author_class_guard_update", sql: wantsGuard.slice(updateIndex) }
|
|
502
|
+
];
|
|
503
|
+
const normalise = (sql) => {
|
|
504
|
+
const before = sql.indexOf("BEFORE");
|
|
505
|
+
if (before < 0) return "";
|
|
506
|
+
let body = sql.slice(before).replace(/\s+/g, " ").trim();
|
|
507
|
+
while (body.endsWith(";")) body = body.slice(0, -1).trimEnd();
|
|
508
|
+
return body;
|
|
509
|
+
};
|
|
510
|
+
let guardIsStale = false;
|
|
511
|
+
for (const wanted of wantedStatements) {
|
|
512
|
+
let installed = "";
|
|
513
|
+
try {
|
|
514
|
+
const row = raw.prepare("SELECT sql FROM sqlite_master WHERE type='trigger' AND name=?").get(wanted.name);
|
|
515
|
+
installed = String(row?.sql ?? "");
|
|
516
|
+
} catch {
|
|
517
|
+
installed = "";
|
|
518
|
+
}
|
|
519
|
+
if (installed === "" || normalise(installed) !== normalise(wanted.sql)) {
|
|
520
|
+
guardIsStale = true;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (guardIsStale) {
|
|
525
|
+
try {
|
|
526
|
+
raw.exec("DROP TRIGGER IF EXISTS brain_annotation_author_class_guard_update; DROP TRIGGER IF EXISTS brain_annotation_author_class_guard;");
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
raw.exec(ANNOTATION_AUTHOR_CLASS_TRIGGER_DDL);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
try {
|
|
534
|
+
raw.prepare("SELECT count(*) FROM sqlite_master WHERE type='trigger'").all();
|
|
535
|
+
} catch (unreadable) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`annotation author-class guard failed AND the schema is unreadable after it: ${String(
|
|
538
|
+
unreadable?.message ?? unreadable
|
|
539
|
+
)}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function ensureEmbeddingBlobColumn(raw) {
|
|
545
|
+
try {
|
|
546
|
+
const cols = raw.prepare("PRAGMA table_info(brain_semantic_embedding)").all().map((c) => c.name);
|
|
547
|
+
if (cols.length === 0) return;
|
|
548
|
+
if (cols.includes("embedding_blob")) return;
|
|
549
|
+
raw.exec("ALTER TABLE brain_semantic_embedding ADD COLUMN embedding_blob BLOB");
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function ensureQueryShapeIndexes(raw) {
|
|
554
|
+
try {
|
|
555
|
+
const names = raw.prepare("PRAGMA index_list(brain_symbol)").all().map((i) => i.name);
|
|
556
|
+
if (names.length === 0) return;
|
|
557
|
+
if (names.includes("brain_symbol_gravity_idx")) return;
|
|
558
|
+
raw.exec(
|
|
559
|
+
"CREATE INDEX IF NOT EXISTS brain_symbol_gravity_idx ON brain_symbol(project_id, gravity_score DESC, line_start)"
|
|
560
|
+
);
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function ensureSymbolDeclarationColumn(raw) {
|
|
565
|
+
try {
|
|
566
|
+
const cols = raw.prepare("PRAGMA table_info(brain_symbol)").all().map((c) => c.name);
|
|
567
|
+
if (cols.length === 0) return;
|
|
568
|
+
if (cols.includes("is_declaration")) return;
|
|
569
|
+
raw.exec("ALTER TABLE brain_symbol ADD COLUMN is_declaration INTEGER");
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function ensureSymbolSupertypesColumn(raw) {
|
|
574
|
+
try {
|
|
575
|
+
const cols = raw.prepare("PRAGMA table_info(brain_symbol)").all().map((c) => c.name);
|
|
576
|
+
if (cols.length === 0) return;
|
|
577
|
+
if (cols.includes("super_types")) return;
|
|
578
|
+
raw.exec("ALTER TABLE brain_symbol ADD COLUMN super_types TEXT");
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function ensureRelationshipTargetBasisColumn(raw) {
|
|
583
|
+
try {
|
|
584
|
+
const hidden = raw.prepare("PRAGMA table_xinfo(brain_relationship)").all();
|
|
585
|
+
if (hidden.length === 0) return;
|
|
586
|
+
if (hidden.some((c) => c.name === "target_basis")) return;
|
|
587
|
+
try {
|
|
588
|
+
raw.prepare("SELECT target_basis FROM brain_relationship LIMIT 1").get();
|
|
589
|
+
return;
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
raw.exec(`ALTER TABLE brain_relationship ADD COLUMN target_basis TEXT GENERATED ALWAYS AS (
|
|
593
|
+
CASE
|
|
594
|
+
WHEN target_symbol_id IS NOT NULL THEN 'symbol'
|
|
595
|
+
WHEN metadata IS NOT NULL AND json_extract(metadata, '$.isExternalTarget') = 1 THEN 'external'
|
|
596
|
+
WHEN target_external IS NOT NULL THEN 'unresolved'
|
|
597
|
+
ELSE 'unresolved'
|
|
598
|
+
END
|
|
599
|
+
) VIRTUAL`);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function ensureRelationshipEvidenceClassColumn(raw) {
|
|
604
|
+
try {
|
|
605
|
+
const hidden = raw.prepare("PRAGMA table_xinfo(brain_relationship)").all();
|
|
606
|
+
if (hidden.length === 0) return;
|
|
607
|
+
if (!hidden.some((c) => c.name === "evidence_class")) {
|
|
608
|
+
try {
|
|
609
|
+
raw.prepare("SELECT evidence_class FROM brain_relationship LIMIT 1").get();
|
|
610
|
+
} catch {
|
|
611
|
+
raw.exec("ALTER TABLE brain_relationship ADD COLUMN evidence_class TEXT GENERATED ALWAYS AS (json_extract(metadata, '$.evidenceClass')) VIRTUAL");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const indexes = raw.prepare("PRAGMA index_list(brain_relationship)").all().map((i) => i.name);
|
|
615
|
+
if (indexes.includes("brain_rel_pattern_cover_idx")) {
|
|
616
|
+
const columns = raw.prepare("PRAGMA index_info(brain_rel_pattern_cover_idx)").all().map((c) => c.name);
|
|
617
|
+
if (!columns.includes("target_external")) {
|
|
618
|
+
raw.exec("DROP INDEX IF EXISTS brain_rel_pattern_cover_idx");
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
raw.exec("CREATE INDEX IF NOT EXISTS brain_rel_pattern_cover_idx ON brain_relationship(project_id, relationship_type, target_symbol_id, source_symbol_id, confidence, evidence_class, target_external)");
|
|
622
|
+
const symbolIndexes = raw.prepare("PRAGMA index_list(brain_symbol)").all().map((i) => i.name);
|
|
623
|
+
if (!symbolIndexes.includes("brain_symbol_type_cover_idx")) {
|
|
624
|
+
raw.exec("CREATE INDEX IF NOT EXISTS brain_symbol_type_cover_idx ON brain_symbol(project_id, symbol_type)");
|
|
625
|
+
}
|
|
626
|
+
} catch {
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function ensureSymbolSourceTextColumn(raw) {
|
|
630
|
+
try {
|
|
631
|
+
const cols = raw.prepare("PRAGMA table_info(brain_symbol)").all().map((c) => c.name);
|
|
632
|
+
if (cols.length === 0) return;
|
|
633
|
+
if (cols.includes("source_text")) return;
|
|
634
|
+
raw.exec("ALTER TABLE brain_symbol ADD COLUMN source_text TEXT");
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function ensureFileMetadataColumn(raw) {
|
|
639
|
+
try {
|
|
640
|
+
const cols = raw.prepare("PRAGMA table_info(brain_file)").all().map((c) => c.name);
|
|
641
|
+
if (cols.length === 0) return;
|
|
642
|
+
if (cols.includes("metadata")) return;
|
|
643
|
+
raw.exec("ALTER TABLE brain_file ADD COLUMN metadata TEXT");
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function ensureHealthIndexes(raw) {
|
|
648
|
+
try {
|
|
649
|
+
raw.exec(`CREATE INDEX IF NOT EXISTS brain_embed_health_idx ON brain_semantic_embedding(project_id, embedding_model, entity_id, vector_dimension)`);
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
const coreIdx = [
|
|
653
|
+
["brain_symbol_owner_idx", "brain_symbol(owner_symbol_id)"],
|
|
654
|
+
["brain_symbol_project_owner_idx", "brain_symbol(project_id, owner_symbol_id)"],
|
|
655
|
+
["brain_symbol_project_idx", "brain_symbol(project_id)"],
|
|
656
|
+
["brain_symbol_name_idx", "brain_symbol(name)"],
|
|
657
|
+
["brain_rel_proj_type_target_idx", "brain_relationship(project_id, relationship_type, target_symbol_id)"],
|
|
658
|
+
["brain_rel_source_idx", "brain_relationship(source_symbol_id)"],
|
|
659
|
+
["brain_rel_target_idx", "brain_relationship(target_symbol_id)"],
|
|
660
|
+
["brain_file_project_idx", "brain_file(project_id)"],
|
|
661
|
+
["brain_file_path_idx", "brain_file(path)"]
|
|
662
|
+
];
|
|
663
|
+
let recreated = 0;
|
|
664
|
+
for (const [name, cols] of coreIdx) {
|
|
665
|
+
try {
|
|
666
|
+
const before = raw.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name=?").get(name);
|
|
667
|
+
raw.exec(`CREATE INDEX IF NOT EXISTS ${name} ON ${cols}`);
|
|
668
|
+
if (!before) recreated++;
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return { recreated };
|
|
673
|
+
}
|
|
674
|
+
function ensurePlannerStats(raw) {
|
|
675
|
+
try {
|
|
676
|
+
const hasStats = raw.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_stat1'").get();
|
|
677
|
+
if (hasStats) return { analyzed: false };
|
|
678
|
+
if (process.env.BB_SKIP_ANALYZE === "1") return { analyzed: false };
|
|
679
|
+
raw.exec("ANALYZE");
|
|
680
|
+
return { analyzed: true };
|
|
681
|
+
} catch {
|
|
682
|
+
return { analyzed: false };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
var fkHealedHandles = /* @__PURE__ */ new WeakSet();
|
|
686
|
+
function cleanupOrphanedFkRows(raw) {
|
|
687
|
+
const handle = raw?.native ?? raw;
|
|
688
|
+
if (handle && typeof handle === "object" && fkHealedHandles.has(handle)) {
|
|
689
|
+
return { relationships: 0, imports: 0, symbols: 0 };
|
|
690
|
+
}
|
|
691
|
+
let relationships = 0;
|
|
692
|
+
let imports = 0;
|
|
693
|
+
let symbols = 0;
|
|
694
|
+
try {
|
|
695
|
+
const r = raw.prepare(`DELETE FROM brain_relationship WHERE source_symbol_id IN (
|
|
696
|
+
SELECT r.source_symbol_id FROM brain_relationship r
|
|
697
|
+
LEFT JOIN brain_symbol s ON s.id = r.source_symbol_id
|
|
698
|
+
WHERE s.id IS NULL
|
|
699
|
+
)`).run();
|
|
700
|
+
relationships = r?.changes ?? 0;
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
const i = raw.prepare(`DELETE FROM brain_import WHERE file_id IN (
|
|
705
|
+
SELECT i.file_id FROM brain_import i
|
|
706
|
+
LEFT JOIN brain_file f ON f.id = i.file_id
|
|
707
|
+
WHERE f.id IS NULL
|
|
708
|
+
)`).run();
|
|
709
|
+
imports = i?.changes ?? 0;
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const s = raw.prepare(`DELETE FROM brain_symbol WHERE file_id IN (
|
|
714
|
+
SELECT sy.file_id FROM brain_symbol sy
|
|
715
|
+
LEFT JOIN brain_file f ON f.id = sy.file_id
|
|
716
|
+
WHERE f.id IS NULL
|
|
717
|
+
)`).run();
|
|
718
|
+
symbols = s?.changes ?? 0;
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
if (handle && typeof handle === "object") fkHealedHandles.add(handle);
|
|
722
|
+
return { relationships, imports, symbols };
|
|
723
|
+
}
|
|
724
|
+
function migrateSchemaColumns(raw) {
|
|
725
|
+
try {
|
|
726
|
+
const cols = new Set(raw.prepare("PRAGMA table_info(brain_index_status)").all().map((c) => c.name));
|
|
727
|
+
const expected = [
|
|
728
|
+
["status", "TEXT"],
|
|
729
|
+
["last_full_index", "INTEGER"],
|
|
730
|
+
["last_incremental_index", "INTEGER"],
|
|
731
|
+
["total_files", "INTEGER NOT NULL DEFAULT 0"],
|
|
732
|
+
["indexed_files", "INTEGER NOT NULL DEFAULT 0"],
|
|
733
|
+
["total_symbols", "INTEGER NOT NULL DEFAULT 0"],
|
|
734
|
+
["total_relationships", "INTEGER NOT NULL DEFAULT 0"],
|
|
735
|
+
["time_created", "INTEGER"],
|
|
736
|
+
["time_updated", "INTEGER"],
|
|
737
|
+
// M23.P3: codebase identity for index portability (adopt-on-move).
|
|
738
|
+
["codebase_fingerprint", "TEXT"],
|
|
739
|
+
["codebase_git", "TEXT"]
|
|
740
|
+
];
|
|
741
|
+
for (const [name, ddl] of expected) {
|
|
742
|
+
if (!cols.has(name)) {
|
|
743
|
+
raw.exec(`ALTER TABLE brain_index_status ADD COLUMN ${name} ${ddl}`);
|
|
744
|
+
cols.add(name);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function ensureSchema(raw) {
|
|
751
|
+
raw.exec(DDL);
|
|
752
|
+
ensureHealthIndexes(raw);
|
|
753
|
+
ensureEmbeddingBlobColumn(raw);
|
|
754
|
+
ensureSymbolDeclarationColumn(raw);
|
|
755
|
+
ensureSymbolSupertypesColumn(raw);
|
|
756
|
+
ensureSymbolSourceTextColumn(raw);
|
|
757
|
+
ensureRelationshipTargetBasisColumn(raw);
|
|
758
|
+
ensureRelationshipEvidenceClassColumn(raw);
|
|
759
|
+
try {
|
|
760
|
+
raw.prepare("INSERT OR IGNORE INTO brain_schema_version (version, applied_at, migration_name) VALUES (?, strftime('%s','now') * 1000, ?)").run(SCHEMA_VERSION, SCHEMA_MIGRATION_NAME);
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
migrateSchemaColumns(raw);
|
|
764
|
+
ensureAnnotationTable(raw);
|
|
765
|
+
raw.exec(`
|
|
766
|
+
CREATE TABLE IF NOT EXISTS brain_analysis_run (
|
|
767
|
+
id TEXT PRIMARY KEY NOT NULL, project_id TEXT NOT NULL, kind TEXT NOT NULL,
|
|
768
|
+
status TEXT NOT NULL, engine_version TEXT NOT NULL, input_hash TEXT NOT NULL,
|
|
769
|
+
started_at INTEGER NOT NULL, completed_at INTEGER, finding_count INTEGER NOT NULL DEFAULT 0,
|
|
770
|
+
error TEXT, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL
|
|
771
|
+
);
|
|
772
|
+
CREATE TABLE IF NOT EXISTS brain_analysis_finding (
|
|
773
|
+
id TEXT PRIMARY KEY NOT NULL, project_id TEXT NOT NULL, kind TEXT NOT NULL,
|
|
774
|
+
fingerprint TEXT NOT NULL, rule TEXT NOT NULL, title TEXT NOT NULL, severity TEXT NOT NULL,
|
|
775
|
+
state TEXT NOT NULL, confidence REAL, evidence TEXT, first_seen_at INTEGER NOT NULL,
|
|
776
|
+
last_seen_at INTEGER NOT NULL, resolved_at INTEGER, suppressed_at INTEGER, mitigated_at INTEGER,
|
|
777
|
+
occurrence_count INTEGER NOT NULL DEFAULT 0, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL
|
|
778
|
+
);
|
|
779
|
+
CREATE TABLE IF NOT EXISTS brain_analysis_occurrence (
|
|
780
|
+
id TEXT PRIMARY KEY NOT NULL, finding_id TEXT NOT NULL, run_id TEXT NOT NULL, state TEXT NOT NULL,
|
|
781
|
+
location TEXT, evidence TEXT, observed_at INTEGER NOT NULL, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL
|
|
782
|
+
);
|
|
783
|
+
CREATE UNIQUE INDEX IF NOT EXISTS brain_analysis_finding_identity_idx ON brain_analysis_finding(project_id, kind, fingerprint);
|
|
784
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_run_project_idx ON brain_analysis_run(project_id);
|
|
785
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_run_kind_idx ON brain_analysis_run(kind);
|
|
786
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_run_started_idx ON brain_analysis_run(started_at);
|
|
787
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_finding_project_idx ON brain_analysis_finding(project_id);
|
|
788
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_finding_state_idx ON brain_analysis_finding(project_id, state);
|
|
789
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_occurrence_finding_idx ON brain_analysis_occurrence(finding_id);
|
|
790
|
+
CREATE INDEX IF NOT EXISTS brain_analysis_occurrence_run_idx ON brain_analysis_occurrence(run_id);
|
|
791
|
+
`);
|
|
792
|
+
try {
|
|
793
|
+
raw.prepare(`INSERT OR IGNORE INTO brain_index_status (project_id, indexing_version, languages_detected, frameworks_detected, indexing_errors, performance_stats, time_created, time_updated)
|
|
794
|
+
VALUES ('__schema__', 'v1.0.0', '[]', '[]', '[]', '{}', strftime('%s','now') * 1000, strftime('%s','now') * 1000)`).run();
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/brain/database.ts
|
|
800
|
+
var rawInstances = /* @__PURE__ */ new Map();
|
|
801
|
+
var drizzleInstances = /* @__PURE__ */ new Map();
|
|
802
|
+
var pathOverrides = /* @__PURE__ */ new Map();
|
|
803
|
+
function evictStaleRawHandle(cacheKey) {
|
|
804
|
+
if (rawInstances.has(cacheKey)) rawInstances.delete(cacheKey);
|
|
805
|
+
}
|
|
806
|
+
function resolveDbPath(projectId) {
|
|
807
|
+
return pathOverrides.get(projectId) ?? BrainDatabase.getDbPath(projectId);
|
|
808
|
+
}
|
|
809
|
+
function isBun() {
|
|
810
|
+
return typeof globalThis.Bun !== "undefined";
|
|
811
|
+
}
|
|
812
|
+
async function buildDrizzle(raw) {
|
|
813
|
+
if (isBun()) {
|
|
814
|
+
const { drizzle: drizzle2 } = await import("drizzle-orm/bun-sqlite");
|
|
815
|
+
return drizzle2({ client: raw.native }, { schema: brain_sql_exports });
|
|
816
|
+
}
|
|
817
|
+
const { drizzle } = await import("drizzle-orm/better-sqlite3");
|
|
818
|
+
return drizzle({ client: raw.native }, { schema: brain_sql_exports });
|
|
819
|
+
}
|
|
820
|
+
function ensureSchemaIfNew(raw) {
|
|
821
|
+
const hasFile = raw.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='brain_file'").get();
|
|
822
|
+
if (!hasFile) {
|
|
823
|
+
ensureSchema(raw);
|
|
824
|
+
} else {
|
|
825
|
+
migrateSchemaColumns(raw);
|
|
826
|
+
const idxHeal = ensureHealthIndexes(raw);
|
|
827
|
+
if (idxHeal.recreated > 0) {
|
|
828
|
+
try {
|
|
829
|
+
raw.exec("ANALYZE");
|
|
830
|
+
} catch {
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
ensurePlannerStats(raw);
|
|
834
|
+
cleanupOrphanedFkRows(raw);
|
|
835
|
+
ensureAnnotationTable(raw);
|
|
836
|
+
ensureEmbeddingBlobColumn(raw);
|
|
837
|
+
ensureSymbolDeclarationColumn(raw);
|
|
838
|
+
ensureSymbolSupertypesColumn(raw);
|
|
839
|
+
ensureFileMetadataColumn(raw);
|
|
840
|
+
ensureSymbolSourceTextColumn(raw);
|
|
841
|
+
ensureRelationshipTargetBasisColumn(raw);
|
|
842
|
+
ensureRelationshipEvidenceClassColumn(raw);
|
|
843
|
+
ensureQueryShapeIndexes(raw);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
var BrainDatabase;
|
|
847
|
+
((BrainDatabase2) => {
|
|
848
|
+
function registerPath(projectId, dbPath) {
|
|
849
|
+
pathOverrides.set(projectId, dbPath);
|
|
850
|
+
}
|
|
851
|
+
BrainDatabase2.registerPath = registerPath;
|
|
852
|
+
function clearPathOverrides() {
|
|
853
|
+
pathOverrides.clear();
|
|
854
|
+
}
|
|
855
|
+
BrainDatabase2.clearPathOverrides = clearPathOverrides;
|
|
856
|
+
function getDbPath(projectId) {
|
|
857
|
+
return path.join(bbProjectsDir(), projectId, "brain.db");
|
|
858
|
+
}
|
|
859
|
+
BrainDatabase2.getDbPath = getDbPath;
|
|
860
|
+
function getDbDirectory(projectId) {
|
|
861
|
+
return path.dirname(getDbPath(projectId));
|
|
862
|
+
}
|
|
863
|
+
BrainDatabase2.getDbDirectory = getDbDirectory;
|
|
864
|
+
function getPath(projectId) {
|
|
865
|
+
return getDbPath(projectId);
|
|
866
|
+
}
|
|
867
|
+
BrainDatabase2.getPath = getPath;
|
|
868
|
+
function getDirectory(projectId) {
|
|
869
|
+
return getDbDirectory(projectId);
|
|
870
|
+
}
|
|
871
|
+
BrainDatabase2.getDirectory = getDirectory;
|
|
872
|
+
function openByPath(dbPath, options = {}) {
|
|
873
|
+
const key = `${options.readonly ? "ro:" : "rw:"}${dbPath}`;
|
|
874
|
+
const cached = rawInstances.get(key);
|
|
875
|
+
if (cached && !existsSync(dbPath)) evictStaleRawHandle(key);
|
|
876
|
+
const fresh = rawInstances.get(key);
|
|
877
|
+
if (fresh) return fresh;
|
|
878
|
+
const db = openDatabase(dbPath, options);
|
|
879
|
+
if (!options.readonly) ensureSchemaIfNew(db);
|
|
880
|
+
rawInstances.set(key, db);
|
|
881
|
+
return db;
|
|
882
|
+
}
|
|
883
|
+
BrainDatabase2.openByPath = openByPath;
|
|
884
|
+
async function getInstance(projectId) {
|
|
885
|
+
const dbPath = resolveDbPath(projectId);
|
|
886
|
+
if (!existsSync(path.dirname(dbPath))) mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
887
|
+
if (!existsSync(dbPath)) {
|
|
888
|
+
drizzleInstances.delete(projectId);
|
|
889
|
+
evictStaleRawHandle(dbPath);
|
|
890
|
+
}
|
|
891
|
+
if (drizzleInstances.has(projectId)) return drizzleInstances.get(projectId);
|
|
892
|
+
const cachedRaw = rawInstances.get(dbPath);
|
|
893
|
+
const raw = cachedRaw ?? openDatabase(dbPath);
|
|
894
|
+
ensureSchemaIfNew(raw);
|
|
895
|
+
if (!cachedRaw) cleanupOrphanedFkRows(raw);
|
|
896
|
+
rawInstances.set(dbPath, raw);
|
|
897
|
+
const drizzleDb = await buildDrizzle(raw);
|
|
898
|
+
drizzleInstances.set(projectId, drizzleDb);
|
|
899
|
+
return drizzleDb;
|
|
900
|
+
}
|
|
901
|
+
BrainDatabase2.getInstance = getInstance;
|
|
902
|
+
BrainDatabase2.initialize = getInstance;
|
|
903
|
+
async function getReadOnlyInstance(projectId) {
|
|
904
|
+
const key = `ro:${projectId}`;
|
|
905
|
+
const cached = drizzleInstances.get(key);
|
|
906
|
+
if (cached) return cached;
|
|
907
|
+
const raw = await getRawInstance(projectId, { readonly: true });
|
|
908
|
+
const drizzleDb = await buildDrizzle(raw);
|
|
909
|
+
drizzleInstances.set(key, drizzleDb);
|
|
910
|
+
return drizzleDb;
|
|
911
|
+
}
|
|
912
|
+
BrainDatabase2.getReadOnlyInstance = getReadOnlyInstance;
|
|
913
|
+
async function getRawInstance(projectId, options = {}) {
|
|
914
|
+
const dbPath = resolveDbPath(projectId);
|
|
915
|
+
if (options.readonly) {
|
|
916
|
+
const roKey = `ro:${dbPath}`;
|
|
917
|
+
if (!existsSync(dbPath)) evictStaleRawHandle(roKey);
|
|
918
|
+
const cachedRo = rawInstances.get(roKey);
|
|
919
|
+
if (cachedRo) return cachedRo;
|
|
920
|
+
if (!existsSync(dbPath)) {
|
|
921
|
+
throw new Error(`no index found for project ${projectId}: ${dbPath} \u2014 run: bb index <project>`);
|
|
922
|
+
}
|
|
923
|
+
const ro = openDatabase(dbPath, { readonly: true });
|
|
924
|
+
const hasFile = ro.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='brain_file'").get();
|
|
925
|
+
if (!hasFile) {
|
|
926
|
+
try {
|
|
927
|
+
ro.close();
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
throw new Error(`index not initialised for project ${projectId}: ${dbPath} \u2014 run: bb index <project>`);
|
|
931
|
+
}
|
|
932
|
+
rawInstances.set(roKey, ro);
|
|
933
|
+
return ro;
|
|
934
|
+
}
|
|
935
|
+
if (!existsSync(path.dirname(dbPath))) mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
936
|
+
if (!existsSync(dbPath)) evictStaleRawHandle(dbPath);
|
|
937
|
+
const raw = rawInstances.get(dbPath) ?? openDatabase(dbPath);
|
|
938
|
+
ensureSchemaIfNew(raw);
|
|
939
|
+
rawInstances.set(dbPath, raw);
|
|
940
|
+
return raw;
|
|
941
|
+
}
|
|
942
|
+
BrainDatabase2.getRawInstance = getRawInstance;
|
|
943
|
+
function getMemoryStats() {
|
|
944
|
+
const mem = process.memoryUsage();
|
|
945
|
+
return { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal };
|
|
946
|
+
}
|
|
947
|
+
BrainDatabase2.getMemoryStats = getMemoryStats;
|
|
948
|
+
function close(projectId) {
|
|
949
|
+
const dbPath = resolveDbPath(projectId);
|
|
950
|
+
drizzleInstances.delete(projectId);
|
|
951
|
+
const raw = rawInstances.get(dbPath);
|
|
952
|
+
if (raw) {
|
|
953
|
+
try {
|
|
954
|
+
raw.close();
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
rawInstances.delete(dbPath);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
BrainDatabase2.close = close;
|
|
961
|
+
function closeAll() {
|
|
962
|
+
for (const db of rawInstances.values()) {
|
|
963
|
+
try {
|
|
964
|
+
db.close();
|
|
965
|
+
} catch {
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
rawInstances.clear();
|
|
969
|
+
drizzleInstances.clear();
|
|
970
|
+
}
|
|
971
|
+
BrainDatabase2.closeAll = closeAll;
|
|
972
|
+
function closeByPath(dbPath) {
|
|
973
|
+
const key = `ro:${dbPath}`;
|
|
974
|
+
const raw = rawInstances.get(key);
|
|
975
|
+
if (raw) {
|
|
976
|
+
try {
|
|
977
|
+
raw.close();
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
rawInstances.delete(key);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
BrainDatabase2.closeByPath = closeByPath;
|
|
984
|
+
async function getStats(projectId) {
|
|
985
|
+
const raw = await getRawInstance(projectId);
|
|
986
|
+
const files = raw.prepare("SELECT COUNT(*) as c FROM brain_file WHERE project_id = ?").get(projectId)?.c ?? 0;
|
|
987
|
+
const symbols = raw.prepare("SELECT COUNT(*) as c FROM brain_symbol WHERE project_id = ?").get(projectId)?.c ?? 0;
|
|
988
|
+
const relationships = raw.prepare("SELECT COUNT(*) as c FROM brain_relationship WHERE project_id = ?").get(projectId)?.c ?? 0;
|
|
989
|
+
const imports = raw.prepare("SELECT COUNT(*) as c FROM brain_import WHERE project_id = ?").get(projectId)?.c ?? 0;
|
|
990
|
+
return { files, symbols, relationships, imports };
|
|
991
|
+
}
|
|
992
|
+
BrainDatabase2.getStats = getStats;
|
|
993
|
+
const MAX_INDEXING_ERRORS = 1e3;
|
|
994
|
+
const MAX_PERF_RUNS = 10;
|
|
995
|
+
function parseJsonArray(value) {
|
|
996
|
+
if (Array.isArray(value)) return value;
|
|
997
|
+
if (typeof value !== "string") return [];
|
|
998
|
+
try {
|
|
999
|
+
const parsed = JSON.parse(value);
|
|
1000
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1001
|
+
} catch {
|
|
1002
|
+
return [];
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
async function updateIndexStatus(projectId, data) {
|
|
1006
|
+
const raw = await getRawInstance(projectId);
|
|
1007
|
+
const existing = raw.prepare("SELECT indexing_errors, performance_stats FROM brain_index_status WHERE project_id = ?").get(projectId);
|
|
1008
|
+
let mergedErrors = parseJsonArray(existing?.indexing_errors);
|
|
1009
|
+
if (data.errors && data.errors.length > 0) {
|
|
1010
|
+
mergedErrors = [...mergedErrors, ...data.errors].slice(-MAX_INDEXING_ERRORS);
|
|
1011
|
+
} else if (data.error) {
|
|
1012
|
+
const legacy = {
|
|
1013
|
+
phase: "index",
|
|
1014
|
+
message: data.error,
|
|
1015
|
+
timestamp: Date.now()
|
|
1016
|
+
};
|
|
1017
|
+
mergedErrors = [...mergedErrors, legacy].slice(-MAX_INDEXING_ERRORS);
|
|
1018
|
+
}
|
|
1019
|
+
const errorsJson = JSON.stringify(mergedErrors);
|
|
1020
|
+
let perfJson = null;
|
|
1021
|
+
if (data.performanceStats) {
|
|
1022
|
+
let priorRuns = [];
|
|
1023
|
+
if (existing?.performance_stats) {
|
|
1024
|
+
try {
|
|
1025
|
+
const prior = JSON.parse(existing.performance_stats);
|
|
1026
|
+
if (Array.isArray(prior?.runs)) priorRuns = prior.runs;
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
perfJson = JSON.stringify({
|
|
1031
|
+
schemaVersion: 1,
|
|
1032
|
+
runs: [...priorRuns, ...data.performanceStats.runs].slice(-MAX_PERF_RUNS)
|
|
1033
|
+
});
|
|
1034
|
+
} else if (existing?.performance_stats) {
|
|
1035
|
+
perfJson = existing.performance_stats;
|
|
1036
|
+
}
|
|
1037
|
+
const filesTotal = data.filesIndexed ?? data.files ?? 0;
|
|
1038
|
+
const symbolsTotal = data.symbolsIndexed ?? data.symbols ?? 0;
|
|
1039
|
+
const filesParsed = data.filesParsed ?? filesTotal;
|
|
1040
|
+
const relationshipsTotal = data.relationshipsIndexed ?? 0;
|
|
1041
|
+
let fingerprint = null;
|
|
1042
|
+
let gitIdentity = null;
|
|
1043
|
+
if (data.codebaseFingerprintProjectPath) {
|
|
1044
|
+
try {
|
|
1045
|
+
const { computeCheckoutFingerprint } = await import("./fingerprint-XDXFRWO6.js");
|
|
1046
|
+
const cf = await computeCheckoutFingerprint(data.codebaseFingerprintProjectPath);
|
|
1047
|
+
fingerprint = cf.fingerprint;
|
|
1048
|
+
gitIdentity = cf.git;
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
raw.prepare(
|
|
1053
|
+
`INSERT INTO brain_index_status (project_id, indexing_version, status, last_full_index, total_files, indexed_files, total_symbols, total_relationships, indexing_errors, performance_stats, codebase_fingerprint, codebase_git, time_created, time_updated)
|
|
1054
|
+
VALUES (?, 'v1.0.0', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s','now') * 1000, strftime('%s','now') * 1000)
|
|
1055
|
+
ON CONFLICT(project_id) DO UPDATE SET
|
|
1056
|
+
status = COALESCE(excluded.status, status),
|
|
1057
|
+
last_full_index = excluded.last_full_index,
|
|
1058
|
+
total_files = COALESCE(excluded.total_files, total_files),
|
|
1059
|
+
indexed_files = COALESCE(excluded.indexed_files, indexed_files),
|
|
1060
|
+
total_symbols = COALESCE(excluded.total_symbols, total_symbols),
|
|
1061
|
+
total_relationships = COALESCE(excluded.total_relationships, total_relationships),
|
|
1062
|
+
indexing_errors = excluded.indexing_errors,
|
|
1063
|
+
performance_stats = COALESCE(excluded.performance_stats, performance_stats),
|
|
1064
|
+
codebase_fingerprint = COALESCE(excluded.codebase_fingerprint, codebase_fingerprint),
|
|
1065
|
+
codebase_git = COALESCE(excluded.codebase_git, codebase_git),
|
|
1066
|
+
time_updated = excluded.time_updated`
|
|
1067
|
+
).run(
|
|
1068
|
+
projectId,
|
|
1069
|
+
data.status ?? null,
|
|
1070
|
+
data.lastIndexed ?? Date.now(),
|
|
1071
|
+
filesTotal,
|
|
1072
|
+
filesParsed,
|
|
1073
|
+
symbolsTotal,
|
|
1074
|
+
relationshipsTotal,
|
|
1075
|
+
errorsJson,
|
|
1076
|
+
perfJson,
|
|
1077
|
+
fingerprint,
|
|
1078
|
+
gitIdentity
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
BrainDatabase2.updateIndexStatus = updateIndexStatus;
|
|
1082
|
+
async function getIndexStatus(projectId) {
|
|
1083
|
+
const raw = await getRawInstance(projectId);
|
|
1084
|
+
try {
|
|
1085
|
+
const row = raw.prepare("SELECT status FROM brain_index_status WHERE project_id = ?").get(projectId);
|
|
1086
|
+
return { status: row?.status ?? null };
|
|
1087
|
+
} catch {
|
|
1088
|
+
return { status: null };
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
BrainDatabase2.getIndexStatus = getIndexStatus;
|
|
1092
|
+
async function getDurableIndexStatus(projectId) {
|
|
1093
|
+
const raw = await getRawInstance(projectId);
|
|
1094
|
+
try {
|
|
1095
|
+
const row = raw.prepare("SELECT status, indexing_errors, performance_stats FROM brain_index_status WHERE project_id = ?").get(projectId);
|
|
1096
|
+
let performanceStats = null;
|
|
1097
|
+
if (row?.performance_stats) {
|
|
1098
|
+
try {
|
|
1099
|
+
const parsed = JSON.parse(row.performance_stats);
|
|
1100
|
+
performanceStats = parsed && typeof parsed === "object" ? parsed : null;
|
|
1101
|
+
} catch {
|
|
1102
|
+
performanceStats = null;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
status: row?.status ?? null,
|
|
1107
|
+
errors: parseJsonArray(row?.indexing_errors),
|
|
1108
|
+
performanceStats
|
|
1109
|
+
};
|
|
1110
|
+
} catch {
|
|
1111
|
+
return { status: null, errors: [], performanceStats: null };
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
BrainDatabase2.getDurableIndexStatus = getDurableIndexStatus;
|
|
1115
|
+
})(BrainDatabase || (BrainDatabase = {}));
|
|
1116
|
+
|
|
1117
|
+
export {
|
|
1118
|
+
BrainDatabase
|
|
1119
|
+
};
|