brainbank 0.5.0 → 0.7.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/README.md +233 -126
- package/dist/{base-DZWtdgIf.d.ts → base-3SNc_CeY.d.ts} +24 -24
- package/dist/chunk-424UFCY7.js +78 -0
- package/dist/chunk-424UFCY7.js.map +1 -0
- package/dist/{chunk-HNPABX7L.js → chunk-7EZR47JV.js} +1 -1
- package/dist/{chunk-HNPABX7L.js.map → chunk-7EZR47JV.js.map} +1 -1
- package/dist/chunk-B77KABWH.js +41 -0
- package/dist/chunk-B77KABWH.js.map +1 -0
- package/dist/{chunk-MY36UPPQ.js → chunk-DI3H6JVZ.js} +357 -379
- package/dist/chunk-DI3H6JVZ.js.map +1 -0
- package/dist/{chunk-DDECTPRM.js → chunk-FGL32LUJ.js} +20 -14
- package/dist/chunk-FGL32LUJ.js.map +1 -0
- package/dist/{chunk-TTXVJFAE.js → chunk-JRSKWF6K.js} +4 -3
- package/dist/{chunk-TTXVJFAE.js.map → chunk-JRSKWF6K.js.map} +1 -1
- package/dist/{chunk-YRGUIRN5.js → chunk-VQ27YUHH.js} +18 -14
- package/dist/chunk-VQ27YUHH.js.map +1 -0
- package/dist/{chunk-BNV43SEF.js → chunk-VVXYZIIB.js} +5 -5
- package/dist/chunk-VVXYZIIB.js.map +1 -0
- package/dist/chunk-ZNLN2VWV.js +110 -0
- package/dist/chunk-ZNLN2VWV.js.map +1 -0
- package/dist/cli.js +102 -45
- package/dist/cli.js.map +1 -1
- package/dist/code.d.ts +4 -2
- package/dist/code.js +1 -1
- package/dist/docs.d.ts +7 -3
- package/dist/docs.js +1 -1
- package/dist/git.d.ts +4 -2
- package/dist/git.js +1 -1
- package/dist/index.d.ts +77 -17
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/dist/local-embedding-ZIMTK6PU.js +8 -0
- package/dist/local-embedding-ZIMTK6PU.js.map +1 -0
- package/dist/memory.d.ts +2 -2
- package/dist/memory.js +1 -1
- package/dist/notes.d.ts +2 -2
- package/dist/notes.js +1 -1
- package/dist/qwen3-reranker-3MHEENT5.js +8 -0
- package/dist/qwen3-reranker-3MHEENT5.js.map +1 -0
- package/dist/resolve-CUJWY6HP.js +10 -0
- package/dist/resolve-CUJWY6HP.js.map +1 -0
- package/package.json +10 -9
- package/dist/chunk-BNV43SEF.js.map +0 -1
- package/dist/chunk-DDECTPRM.js.map +0 -1
- package/dist/chunk-MY36UPPQ.js.map +0 -1
- package/dist/chunk-YRGUIRN5.js.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
providerKey,
|
|
3
|
+
resolveEmbedding
|
|
4
|
+
} from "./chunk-B77KABWH.js";
|
|
1
5
|
import {
|
|
2
6
|
isIgnoredDir,
|
|
3
7
|
isIgnoredFile,
|
|
4
8
|
isSupported
|
|
5
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FGL32LUJ.js";
|
|
6
10
|
import {
|
|
7
11
|
rerank
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-VQ27YUHH.js";
|
|
9
13
|
import {
|
|
10
14
|
normalizeBM25,
|
|
11
15
|
reciprocalRankFusion,
|
|
@@ -55,7 +59,7 @@ function resolveConfig(partial = {}) {
|
|
|
55
59
|
}
|
|
56
60
|
__name(resolveConfig, "resolveConfig");
|
|
57
61
|
|
|
58
|
-
// src/
|
|
62
|
+
// src/domain/collection.ts
|
|
59
63
|
var Collection = class {
|
|
60
64
|
constructor(_name, _db, _embedding, _hnsw, _vecs, _reranker) {
|
|
61
65
|
this._name = _name;
|
|
@@ -435,76 +439,6 @@ var HNSWIndex = class {
|
|
|
435
439
|
}
|
|
436
440
|
};
|
|
437
441
|
|
|
438
|
-
// src/providers/embeddings/local-embedding.ts
|
|
439
|
-
var LocalEmbedding = class {
|
|
440
|
-
static {
|
|
441
|
-
__name(this, "LocalEmbedding");
|
|
442
|
-
}
|
|
443
|
-
dims = 384;
|
|
444
|
-
_pipeline = null;
|
|
445
|
-
_modelName;
|
|
446
|
-
_cacheDir;
|
|
447
|
-
constructor(options = {}) {
|
|
448
|
-
this._modelName = options.model ?? "Xenova/all-MiniLM-L6-v2";
|
|
449
|
-
this._cacheDir = options.cacheDir ?? ".model-cache";
|
|
450
|
-
}
|
|
451
|
-
_pipelinePromise = null;
|
|
452
|
-
/**
|
|
453
|
-
* Lazy-load the transformer pipeline.
|
|
454
|
-
* Singleton — created once and reused.
|
|
455
|
-
* Promise-deduped to prevent concurrent downloads.
|
|
456
|
-
*/
|
|
457
|
-
async _getPipeline() {
|
|
458
|
-
if (this._pipeline) return this._pipeline;
|
|
459
|
-
if (this._pipelinePromise) return this._pipelinePromise;
|
|
460
|
-
this._pipelinePromise = (async () => {
|
|
461
|
-
const { pipeline, env } = await import("@xenova/transformers");
|
|
462
|
-
env.cacheDir = this._cacheDir;
|
|
463
|
-
env.allowLocalModels = true;
|
|
464
|
-
this._pipeline = await pipeline("feature-extraction", this._modelName, {
|
|
465
|
-
quantized: true
|
|
466
|
-
});
|
|
467
|
-
return this._pipeline;
|
|
468
|
-
})();
|
|
469
|
-
try {
|
|
470
|
-
return await this._pipelinePromise;
|
|
471
|
-
} finally {
|
|
472
|
-
this._pipelinePromise = null;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Embed a single text string.
|
|
477
|
-
* Returns a normalized Float32Array of length 384.
|
|
478
|
-
*/
|
|
479
|
-
async embed(text) {
|
|
480
|
-
const pipe = await this._getPipeline();
|
|
481
|
-
const output = await pipe(text, { pooling: "mean", normalize: true });
|
|
482
|
-
return output.data;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Embed multiple texts using real batch processing.
|
|
486
|
-
* Chunks into groups of BATCH_SIZE to balance throughput vs memory.
|
|
487
|
-
*/
|
|
488
|
-
async embedBatch(texts) {
|
|
489
|
-
if (texts.length === 0) return [];
|
|
490
|
-
const BATCH_SIZE = 32;
|
|
491
|
-
const pipe = await this._getPipeline();
|
|
492
|
-
const results = [];
|
|
493
|
-
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
494
|
-
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
495
|
-
const output = await pipe(batch, { pooling: "mean", normalize: true });
|
|
496
|
-
for (let j = 0; j < batch.length; j++) {
|
|
497
|
-
const start = j * this.dims;
|
|
498
|
-
results.push(output.data.slice(start, start + this.dims));
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return results;
|
|
502
|
-
}
|
|
503
|
-
async close() {
|
|
504
|
-
this._pipeline = null;
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
|
|
508
442
|
// src/search/vector/mmr.ts
|
|
509
443
|
function searchMMR(index, query, vectorCache, k, lambda = 0.7) {
|
|
510
444
|
const candidates = index.search(query, k * 3);
|
|
@@ -827,7 +761,7 @@ var KeywordSearch = class {
|
|
|
827
761
|
}
|
|
828
762
|
};
|
|
829
763
|
|
|
830
|
-
// src/
|
|
764
|
+
// src/search/context-builder.ts
|
|
831
765
|
var ContextBuilder = class {
|
|
832
766
|
constructor(_search, _coEdits) {
|
|
833
767
|
this._search = _search;
|
|
@@ -947,21 +881,21 @@ var ContextBuilder = class {
|
|
|
947
881
|
// src/brainbank.ts
|
|
948
882
|
import { EventEmitter } from "events";
|
|
949
883
|
|
|
950
|
-
// src/
|
|
884
|
+
// src/bootstrap/registry.ts
|
|
951
885
|
var ALIASES = {};
|
|
952
|
-
var
|
|
886
|
+
var PluginRegistry = class {
|
|
953
887
|
static {
|
|
954
|
-
__name(this, "
|
|
888
|
+
__name(this, "PluginRegistry");
|
|
955
889
|
}
|
|
956
890
|
_map = /* @__PURE__ */ new Map();
|
|
957
891
|
// ── Registration ────────────────────────────────
|
|
958
|
-
/** Store
|
|
959
|
-
register(
|
|
960
|
-
this._map.set(
|
|
892
|
+
/** Store a plugin. Duplicate names silently overwrite. */
|
|
893
|
+
register(plugin) {
|
|
894
|
+
this._map.set(plugin.name, plugin);
|
|
961
895
|
}
|
|
962
896
|
// ── Lookup ──────────────────────────────────────
|
|
963
897
|
/**
|
|
964
|
-
* Check whether
|
|
898
|
+
* Check whether a plugin is registered.
|
|
965
899
|
* Supports type-prefix matching: `has('code')` returns true if
|
|
966
900
|
* 'code', 'code:frontend', or 'code:backend' is registered.
|
|
967
901
|
*/
|
|
@@ -973,7 +907,7 @@ var IndexerRegistry = class {
|
|
|
973
907
|
return false;
|
|
974
908
|
}
|
|
975
909
|
/**
|
|
976
|
-
* Get
|
|
910
|
+
* Get a plugin by name. Throws a descriptive error if not found.
|
|
977
911
|
*
|
|
978
912
|
* Resolution order:
|
|
979
913
|
* 1. Alias map (currently empty)
|
|
@@ -987,11 +921,11 @@ var IndexerRegistry = class {
|
|
|
987
921
|
const prefixed = this.firstByType(name);
|
|
988
922
|
if (prefixed) return prefixed;
|
|
989
923
|
throw new Error(
|
|
990
|
-
`BrainBank:
|
|
924
|
+
`BrainBank: Plugin '${name}' is not loaded. Add .use(${name}()) to your BrainBank instance.`
|
|
991
925
|
);
|
|
992
926
|
}
|
|
993
927
|
/**
|
|
994
|
-
* Return every
|
|
928
|
+
* Return every plugin whose name equals `type` or starts with `type + ':'`.
|
|
995
929
|
* Example: allByType('code') → [code, code:frontend, code:backend]
|
|
996
930
|
*/
|
|
997
931
|
allByType(type) {
|
|
@@ -999,7 +933,7 @@ var IndexerRegistry = class {
|
|
|
999
933
|
(m) => m.name === type || m.name.startsWith(type + ":")
|
|
1000
934
|
);
|
|
1001
935
|
}
|
|
1002
|
-
/** Return the first
|
|
936
|
+
/** Return the first plugin that matches the type prefix, or undefined. */
|
|
1003
937
|
firstByType(type) {
|
|
1004
938
|
for (const m of this._map.values()) {
|
|
1005
939
|
if (m.name === type || m.name.startsWith(type + ":")) return m;
|
|
@@ -1007,11 +941,11 @@ var IndexerRegistry = class {
|
|
|
1007
941
|
return void 0;
|
|
1008
942
|
}
|
|
1009
943
|
// ── Accessors ───────────────────────────────────
|
|
1010
|
-
/** All registered
|
|
944
|
+
/** All registered plugin names (insertion order). */
|
|
1011
945
|
get names() {
|
|
1012
946
|
return [...this._map.keys()];
|
|
1013
947
|
}
|
|
1014
|
-
/** All registered
|
|
948
|
+
/** All registered plugin instances (insertion order). */
|
|
1015
949
|
get all() {
|
|
1016
950
|
return [...this._map.values()];
|
|
1017
951
|
}
|
|
@@ -1023,13 +957,13 @@ var IndexerRegistry = class {
|
|
|
1023
957
|
return this._map;
|
|
1024
958
|
}
|
|
1025
959
|
// ── Lifecycle ───────────────────────────────────
|
|
1026
|
-
/** Remove all registered
|
|
960
|
+
/** Remove all registered plugins. Called by BrainBank.close(). */
|
|
1027
961
|
clear() {
|
|
1028
962
|
this._map.clear();
|
|
1029
963
|
}
|
|
1030
964
|
};
|
|
1031
965
|
|
|
1032
|
-
// src/
|
|
966
|
+
// src/bootstrap/initializer.ts
|
|
1033
967
|
import { dirname as dirname2, join as join2 } from "path";
|
|
1034
968
|
|
|
1035
969
|
// src/db/database.ts
|
|
@@ -1401,165 +1335,7 @@ var Database = class {
|
|
|
1401
1335
|
}
|
|
1402
1336
|
};
|
|
1403
1337
|
|
|
1404
|
-
// src/services/
|
|
1405
|
-
var TABLES = [
|
|
1406
|
-
{
|
|
1407
|
-
name: "code",
|
|
1408
|
-
textTable: "code_chunks",
|
|
1409
|
-
vectorTable: "code_vectors",
|
|
1410
|
-
idColumn: "id",
|
|
1411
|
-
fkColumn: "chunk_id",
|
|
1412
|
-
textBuilder: /* @__PURE__ */ __name((r) => [
|
|
1413
|
-
`File: ${r.file_path}`,
|
|
1414
|
-
r.name ? `${r.chunk_type}: ${r.name}` : r.chunk_type,
|
|
1415
|
-
r.content
|
|
1416
|
-
].join("\n"), "textBuilder")
|
|
1417
|
-
},
|
|
1418
|
-
{
|
|
1419
|
-
name: "git",
|
|
1420
|
-
textTable: "git_commits",
|
|
1421
|
-
vectorTable: "git_vectors",
|
|
1422
|
-
idColumn: "id",
|
|
1423
|
-
fkColumn: "commit_id",
|
|
1424
|
-
// Must match git-engine.ts:119-125 exactly
|
|
1425
|
-
textBuilder: /* @__PURE__ */ __name((r) => [
|
|
1426
|
-
`Commit: ${r.message}`,
|
|
1427
|
-
`Author: ${r.author}`,
|
|
1428
|
-
`Date: ${r.date}`,
|
|
1429
|
-
r.files_json && r.files_json !== "[]" ? `Files: ${JSON.parse(r.files_json).join(", ")}` : "",
|
|
1430
|
-
r.diff ? `Changes:
|
|
1431
|
-
${r.diff.slice(0, 2e3)}` : ""
|
|
1432
|
-
].filter(Boolean).join("\n"), "textBuilder")
|
|
1433
|
-
},
|
|
1434
|
-
{
|
|
1435
|
-
name: "memory",
|
|
1436
|
-
textTable: "memory_patterns",
|
|
1437
|
-
vectorTable: "memory_vectors",
|
|
1438
|
-
idColumn: "id",
|
|
1439
|
-
fkColumn: "pattern_id",
|
|
1440
|
-
// Must match memory/pattern-store.ts:49 exactly
|
|
1441
|
-
textBuilder: /* @__PURE__ */ __name((r) => `${r.task_type} ${r.task} ${r.approach}`, "textBuilder")
|
|
1442
|
-
},
|
|
1443
|
-
{
|
|
1444
|
-
name: "notes",
|
|
1445
|
-
textTable: "note_memories",
|
|
1446
|
-
vectorTable: "note_vectors",
|
|
1447
|
-
idColumn: "id",
|
|
1448
|
-
fkColumn: "note_id",
|
|
1449
|
-
// Must match notes/engine.ts:90 exactly
|
|
1450
|
-
textBuilder: /* @__PURE__ */ __name((r) => {
|
|
1451
|
-
const decisions = JSON.parse(r.decisions_json || "[]").join(". ");
|
|
1452
|
-
const patterns = JSON.parse(r.patterns_json || "[]").join(". ");
|
|
1453
|
-
return `${r.title}
|
|
1454
|
-
${r.summary}
|
|
1455
|
-
${decisions}
|
|
1456
|
-
${patterns}`;
|
|
1457
|
-
}, "textBuilder")
|
|
1458
|
-
},
|
|
1459
|
-
{
|
|
1460
|
-
name: "docs",
|
|
1461
|
-
textTable: "doc_chunks",
|
|
1462
|
-
vectorTable: "doc_vectors",
|
|
1463
|
-
idColumn: "id",
|
|
1464
|
-
fkColumn: "chunk_id",
|
|
1465
|
-
// Must match docs-engine.ts:160 exactly
|
|
1466
|
-
textBuilder: /* @__PURE__ */ __name((r) => `title: ${r.title ?? ""} | text: ${r.content}`, "textBuilder")
|
|
1467
|
-
},
|
|
1468
|
-
{
|
|
1469
|
-
name: "kv",
|
|
1470
|
-
textTable: "kv_data",
|
|
1471
|
-
vectorTable: "kv_vectors",
|
|
1472
|
-
idColumn: "id",
|
|
1473
|
-
fkColumn: "data_id",
|
|
1474
|
-
textBuilder: /* @__PURE__ */ __name((r) => r.content, "textBuilder")
|
|
1475
|
-
}
|
|
1476
|
-
];
|
|
1477
|
-
async function reembedAll(db, embedding, hnswMap, options = {}) {
|
|
1478
|
-
const { batchSize = 50, onProgress } = options;
|
|
1479
|
-
const result = {};
|
|
1480
|
-
let total = 0;
|
|
1481
|
-
for (const table of TABLES) {
|
|
1482
|
-
const count = await reembedTable(db, embedding, table, batchSize, onProgress);
|
|
1483
|
-
result[table.name] = count;
|
|
1484
|
-
total += count;
|
|
1485
|
-
const entry = hnswMap.get(table.name);
|
|
1486
|
-
if (entry && count > 0) {
|
|
1487
|
-
await rebuildHnsw(db, table, entry.hnsw, entry.vecs);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
const meta = {
|
|
1491
|
-
provider: embedding.constructor?.name ?? "unknown",
|
|
1492
|
-
dims: String(embedding.dims),
|
|
1493
|
-
reembedded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1494
|
-
};
|
|
1495
|
-
const upsert = db.prepare(
|
|
1496
|
-
"INSERT OR REPLACE INTO embedding_meta (key, value) VALUES (?, ?)"
|
|
1497
|
-
);
|
|
1498
|
-
for (const [k, v] of Object.entries(meta)) {
|
|
1499
|
-
upsert.run(k, v);
|
|
1500
|
-
}
|
|
1501
|
-
return {
|
|
1502
|
-
code: result.code ?? 0,
|
|
1503
|
-
git: result.git ?? 0,
|
|
1504
|
-
memory: result.memory ?? 0,
|
|
1505
|
-
notes: result.notes ?? 0,
|
|
1506
|
-
docs: result.docs ?? 0,
|
|
1507
|
-
kv: result.kv ?? 0,
|
|
1508
|
-
total
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
__name(reembedAll, "reembedAll");
|
|
1512
|
-
async function reembedTable(db, embedding, table, batchSize, onProgress) {
|
|
1513
|
-
const totalCount = db.prepare(
|
|
1514
|
-
`SELECT COUNT(*) as c FROM ${table.textTable}`
|
|
1515
|
-
).get().c;
|
|
1516
|
-
if (totalCount === 0) return 0;
|
|
1517
|
-
const tempTable = `_reembed_${table.vectorTable}`;
|
|
1518
|
-
db.exec(`DROP TABLE IF EXISTS ${tempTable}`);
|
|
1519
|
-
db.exec(`CREATE TABLE ${tempTable} AS SELECT * FROM ${table.vectorTable} WHERE 0`);
|
|
1520
|
-
const insertTemp = db.prepare(
|
|
1521
|
-
`INSERT INTO ${tempTable} (${table.fkColumn}, embedding) VALUES (?, ?)`
|
|
1522
|
-
);
|
|
1523
|
-
let processed = 0;
|
|
1524
|
-
try {
|
|
1525
|
-
for (let offset = 0; offset < totalCount; offset += batchSize) {
|
|
1526
|
-
const batch = db.prepare(
|
|
1527
|
-
`SELECT * FROM ${table.textTable} LIMIT ? OFFSET ?`
|
|
1528
|
-
).all(batchSize, offset);
|
|
1529
|
-
const texts = batch.map((r) => table.textBuilder(r));
|
|
1530
|
-
const vectors = await embedding.embedBatch(texts);
|
|
1531
|
-
db.transaction(() => {
|
|
1532
|
-
for (let j = 0; j < batch.length; j++) {
|
|
1533
|
-
insertTemp.run(batch[j][table.idColumn], vecToBuffer(vectors[j]));
|
|
1534
|
-
}
|
|
1535
|
-
});
|
|
1536
|
-
processed += batch.length;
|
|
1537
|
-
onProgress?.(table.name, processed, totalCount);
|
|
1538
|
-
}
|
|
1539
|
-
db.transaction(() => {
|
|
1540
|
-
db.exec(`DELETE FROM ${table.vectorTable}`);
|
|
1541
|
-
db.exec(`INSERT INTO ${table.vectorTable} SELECT * FROM ${tempTable}`);
|
|
1542
|
-
});
|
|
1543
|
-
} finally {
|
|
1544
|
-
db.exec(`DROP TABLE IF EXISTS ${tempTable}`);
|
|
1545
|
-
}
|
|
1546
|
-
return processed;
|
|
1547
|
-
}
|
|
1548
|
-
__name(reembedTable, "reembedTable");
|
|
1549
|
-
async function rebuildHnsw(db, table, hnsw, vecs) {
|
|
1550
|
-
vecs.clear();
|
|
1551
|
-
hnsw.reinit();
|
|
1552
|
-
const rows = db.prepare(
|
|
1553
|
-
`SELECT ${table.fkColumn} as id, embedding FROM ${table.vectorTable}`
|
|
1554
|
-
).all();
|
|
1555
|
-
for (const row of rows) {
|
|
1556
|
-
const buf = Buffer.from(row.embedding);
|
|
1557
|
-
const vec = new Float32Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
1558
|
-
hnsw.add(vec, row.id);
|
|
1559
|
-
vecs.set(row.id, vec);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
__name(rebuildHnsw, "rebuildHnsw");
|
|
1338
|
+
// src/services/embedding-meta.ts
|
|
1563
1339
|
function getEmbeddingMeta(db) {
|
|
1564
1340
|
try {
|
|
1565
1341
|
const provider = db.prepare(
|
|
@@ -1568,8 +1344,15 @@ function getEmbeddingMeta(db) {
|
|
|
1568
1344
|
const dims = db.prepare(
|
|
1569
1345
|
"SELECT value FROM embedding_meta WHERE key = 'dims'"
|
|
1570
1346
|
).get();
|
|
1347
|
+
const key = db.prepare(
|
|
1348
|
+
"SELECT value FROM embedding_meta WHERE key = 'provider_key'"
|
|
1349
|
+
).get();
|
|
1571
1350
|
if (!provider || !dims) return null;
|
|
1572
|
-
return {
|
|
1351
|
+
return {
|
|
1352
|
+
provider: provider.value,
|
|
1353
|
+
dims: Number(dims.value),
|
|
1354
|
+
providerKey: key?.value ?? "local"
|
|
1355
|
+
};
|
|
1573
1356
|
} catch {
|
|
1574
1357
|
return null;
|
|
1575
1358
|
}
|
|
@@ -1581,6 +1364,7 @@ function setEmbeddingMeta(db, embedding) {
|
|
|
1581
1364
|
);
|
|
1582
1365
|
upsert.run("provider", embedding.constructor?.name ?? "unknown");
|
|
1583
1366
|
upsert.run("dims", String(embedding.dims));
|
|
1367
|
+
upsert.run("provider_key", providerKey(embedding));
|
|
1584
1368
|
upsert.run("indexed_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
1585
1369
|
}
|
|
1586
1370
|
__name(setEmbeddingMeta, "setEmbeddingMeta");
|
|
@@ -1597,112 +1381,138 @@ function detectProviderMismatch(db, embedding) {
|
|
|
1597
1381
|
}
|
|
1598
1382
|
__name(detectProviderMismatch, "detectProviderMismatch");
|
|
1599
1383
|
|
|
1600
|
-
// src/
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const mismatch = detectProviderMismatch(db, embedding);
|
|
1605
|
-
if (mismatch?.mismatch && !options.force) {
|
|
1606
|
-
db.close();
|
|
1607
|
-
throw new Error(
|
|
1608
|
-
`BrainBank: Embedding dimension mismatch (stored: ${mismatch.stored}, current: ${mismatch.current}). Run brain.reembed() to re-index with the new provider, or switch back to the original provider.`
|
|
1609
|
-
);
|
|
1384
|
+
// src/bootstrap/initializer.ts
|
|
1385
|
+
var Initializer = class {
|
|
1386
|
+
static {
|
|
1387
|
+
__name(this, "Initializer");
|
|
1610
1388
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
)
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
const kvCount = countRows(db, "kv_vectors");
|
|
1629
|
-
if (kvHnsw.tryLoad(kvIndexPath, kvCount)) {
|
|
1630
|
-
loadVecCache(db, "kv_vectors", "data_id", kvVecs);
|
|
1631
|
-
} else {
|
|
1632
|
-
loadVectors(db, "kv_vectors", "data_id", kvHnsw, kvVecs);
|
|
1389
|
+
_config;
|
|
1390
|
+
_emit;
|
|
1391
|
+
constructor(config, emit) {
|
|
1392
|
+
this._config = config;
|
|
1393
|
+
this._emit = emit;
|
|
1394
|
+
}
|
|
1395
|
+
/** Phase 1: database, embedding provider, KV HNSW index. */
|
|
1396
|
+
async early(options = {}) {
|
|
1397
|
+
const { _config: config } = this;
|
|
1398
|
+
const db = new Database(config.dbPath);
|
|
1399
|
+
const embedding = await this._resolveEmbedding(db);
|
|
1400
|
+
const mismatch = detectProviderMismatch(db, embedding);
|
|
1401
|
+
if (mismatch?.mismatch && !options.force) {
|
|
1402
|
+
db.close();
|
|
1403
|
+
throw new Error(
|
|
1404
|
+
`BrainBank: Embedding dimension mismatch (stored: ${mismatch.stored}, current: ${mismatch.current}). Run brain.reembed() to re-index with the new provider, or switch back to the original provider.`
|
|
1405
|
+
);
|
|
1633
1406
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
for (const mod of registry.all) {
|
|
1637
|
-
await mod.initialize(ctx);
|
|
1638
|
-
}
|
|
1639
|
-
saveAllHnsw(config.dbPath, kvHnsw, sharedHnsw);
|
|
1640
|
-
return buildSearchLayer(db, embedding, config, registry, sharedHnsw);
|
|
1641
|
-
}
|
|
1642
|
-
__name(lateInit, "lateInit");
|
|
1643
|
-
function buildIndexerContext(db, embedding, config, sharedHnsw, skipVectorLoad, getCollection) {
|
|
1644
|
-
return {
|
|
1645
|
-
db,
|
|
1646
|
-
embedding,
|
|
1647
|
-
config,
|
|
1648
|
-
createHnsw: /* @__PURE__ */ __name((maxElements) => new HNSWIndex(
|
|
1407
|
+
setEmbeddingMeta(db, embedding);
|
|
1408
|
+
const kvHnsw = new HNSWIndex(
|
|
1649
1409
|
config.embeddingDims,
|
|
1650
|
-
maxElements ??
|
|
1410
|
+
config.maxElements ?? 5e5,
|
|
1651
1411
|
config.hnswM,
|
|
1652
1412
|
config.hnswEfConstruction,
|
|
1653
1413
|
config.hnswEfSearch
|
|
1654
|
-
)
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1414
|
+
);
|
|
1415
|
+
await kvHnsw.init();
|
|
1416
|
+
const skipVectorLoad = !!(options.force && mismatch?.mismatch);
|
|
1417
|
+
return { db, embedding, kvHnsw, skipVectorLoad };
|
|
1418
|
+
}
|
|
1419
|
+
/** Phase 2: load vectors, run indexers, build search layer. */
|
|
1420
|
+
async late(earlyResult, registry, sharedHnsw, kvVecs, getCollection) {
|
|
1421
|
+
const { _config: config } = this;
|
|
1422
|
+
const { db, embedding, kvHnsw, skipVectorLoad } = earlyResult;
|
|
1423
|
+
if (!skipVectorLoad) {
|
|
1424
|
+
const kvIndexPath = hnswPath(config.dbPath, "kv");
|
|
1425
|
+
const kvCount = countRows(db, "kv_vectors");
|
|
1426
|
+
if (kvHnsw.tryLoad(kvIndexPath, kvCount)) {
|
|
1427
|
+
loadVecCache(db, "kv_vectors", "data_id", kvVecs);
|
|
1662
1428
|
} else {
|
|
1663
|
-
loadVectors(db,
|
|
1429
|
+
loadVectors(db, "kv_vectors", "data_id", kvHnsw, kvVecs);
|
|
1664
1430
|
}
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1431
|
+
}
|
|
1432
|
+
const ctx = this._buildPluginContext(db, embedding, sharedHnsw, skipVectorLoad, getCollection);
|
|
1433
|
+
for (const mod of registry.all) {
|
|
1434
|
+
await mod.initialize(ctx);
|
|
1435
|
+
}
|
|
1436
|
+
saveAllHnsw(config.dbPath, kvHnsw, sharedHnsw);
|
|
1437
|
+
return this._buildSearchLayer(db, embedding, registry, sharedHnsw);
|
|
1438
|
+
}
|
|
1439
|
+
/** Build the PluginContext passed to each plugin's initialize(). */
|
|
1440
|
+
_buildPluginContext(db, embedding, sharedHnsw, skipVectorLoad, getCollection) {
|
|
1441
|
+
const { _config: config } = this;
|
|
1442
|
+
return {
|
|
1443
|
+
db,
|
|
1444
|
+
embedding,
|
|
1445
|
+
config,
|
|
1446
|
+
createHnsw: /* @__PURE__ */ __name((maxElements, dims) => new HNSWIndex(
|
|
1447
|
+
dims ?? config.embeddingDims,
|
|
1671
1448
|
maxElements ?? config.maxElements,
|
|
1672
1449
|
config.hnswM,
|
|
1673
1450
|
config.hnswEfConstruction,
|
|
1674
1451
|
config.hnswEfSearch
|
|
1675
|
-
).init()
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1452
|
+
).init(), "createHnsw"),
|
|
1453
|
+
loadVectors: /* @__PURE__ */ __name((table, idCol, hnsw, cache) => {
|
|
1454
|
+
if (skipVectorLoad) return;
|
|
1455
|
+
const indexName = table.replace("_vectors", "").replace("_chunks", "");
|
|
1456
|
+
const indexPath = hnswPath(config.dbPath, indexName);
|
|
1457
|
+
const rowCount = countRows(db, table);
|
|
1458
|
+
if (hnsw.tryLoad(indexPath, rowCount)) {
|
|
1459
|
+
loadVecCache(db, table, idCol, cache);
|
|
1460
|
+
} else {
|
|
1461
|
+
loadVectors(db, table, idCol, hnsw, cache);
|
|
1462
|
+
}
|
|
1463
|
+
}, "loadVectors"),
|
|
1464
|
+
getOrCreateSharedHnsw: /* @__PURE__ */ __name(async (type, maxElements, dims) => {
|
|
1465
|
+
const existing = sharedHnsw.get(type);
|
|
1466
|
+
if (existing) return { ...existing, isNew: false };
|
|
1467
|
+
const hnswDims = dims ?? config.embeddingDims;
|
|
1468
|
+
const hnsw = await new HNSWIndex(
|
|
1469
|
+
hnswDims,
|
|
1470
|
+
maxElements ?? config.maxElements,
|
|
1471
|
+
config.hnswM,
|
|
1472
|
+
config.hnswEfConstruction,
|
|
1473
|
+
config.hnswEfSearch
|
|
1474
|
+
).init();
|
|
1475
|
+
const vecCache = /* @__PURE__ */ new Map();
|
|
1476
|
+
sharedHnsw.set(type, { hnsw, vecCache });
|
|
1477
|
+
return { hnsw, vecCache, isNew: true };
|
|
1478
|
+
}, "getOrCreateSharedHnsw"),
|
|
1479
|
+
collection: getCollection
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
/** Build VectorSearch + KeywordSearch + ContextBuilder from initialized plugins. */
|
|
1483
|
+
_buildSearchLayer(db, embedding, registry, sharedHnsw) {
|
|
1484
|
+
const { _config: config } = this;
|
|
1485
|
+
const codeMod = sharedHnsw.get("code");
|
|
1486
|
+
const gitMod = sharedHnsw.get("git");
|
|
1487
|
+
const memMod = registry.firstByType("memory");
|
|
1488
|
+
if (!codeMod && !gitMod && !memMod) return {};
|
|
1489
|
+
const search = new VectorSearch({
|
|
1490
|
+
db,
|
|
1491
|
+
codeHnsw: codeMod?.hnsw,
|
|
1492
|
+
gitHnsw: gitMod?.hnsw,
|
|
1493
|
+
patternHnsw: memMod?.hnsw,
|
|
1494
|
+
codeVecs: codeMod?.vecCache ?? /* @__PURE__ */ new Map(),
|
|
1495
|
+
gitVecs: gitMod?.vecCache ?? /* @__PURE__ */ new Map(),
|
|
1496
|
+
patternVecs: memMod?.vecCache ?? /* @__PURE__ */ new Map(),
|
|
1497
|
+
embedding,
|
|
1498
|
+
reranker: config.reranker
|
|
1499
|
+
});
|
|
1500
|
+
const bm25 = new KeywordSearch(db);
|
|
1501
|
+
const firstGit = registry.firstByType("git");
|
|
1502
|
+
const contextBuilder = new ContextBuilder(search, firstGit?.coEdits);
|
|
1503
|
+
return { search, bm25, contextBuilder };
|
|
1504
|
+
}
|
|
1505
|
+
/** Resolve embedding: explicit config > stored DB key > local default. */
|
|
1506
|
+
async _resolveEmbedding(db) {
|
|
1507
|
+
if (this._config.embeddingProvider) return this._config.embeddingProvider;
|
|
1508
|
+
const meta = getEmbeddingMeta(db);
|
|
1509
|
+
if (meta?.providerKey && meta.providerKey !== "local") {
|
|
1510
|
+
this._emit("progress", `Embedding: auto-resolved '${meta.providerKey}' from DB`);
|
|
1511
|
+
return resolveEmbedding(meta.providerKey);
|
|
1512
|
+
}
|
|
1513
|
+
return resolveEmbedding("local");
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1706
1516
|
function hnswPath(dbPath, name) {
|
|
1707
1517
|
return join2(dirname2(dbPath), `hnsw-${name}.index`);
|
|
1708
1518
|
}
|
|
@@ -1750,7 +1560,7 @@ function loadVecCache(db, table, idCol, cache) {
|
|
|
1750
1560
|
}
|
|
1751
1561
|
__name(loadVecCache, "loadVecCache");
|
|
1752
1562
|
|
|
1753
|
-
// src/
|
|
1563
|
+
// src/api/search-api.ts
|
|
1754
1564
|
var SearchAPI = class {
|
|
1755
1565
|
constructor(_d) {
|
|
1756
1566
|
this._d = _d;
|
|
@@ -1761,7 +1571,7 @@ var SearchAPI = class {
|
|
|
1761
1571
|
// ── Vector ──────────────────────────────────────
|
|
1762
1572
|
async search(query, options) {
|
|
1763
1573
|
if (!this._d.search) {
|
|
1764
|
-
return this._d.registry.has("docs") ? this.
|
|
1574
|
+
return this._d.registry.has("docs") ? await this._searchDocs(query, { k: 8 }) : [];
|
|
1765
1575
|
}
|
|
1766
1576
|
return this._d.search.search(query, options);
|
|
1767
1577
|
}
|
|
@@ -1789,18 +1599,18 @@ var SearchAPI = class {
|
|
|
1789
1599
|
if (this._d.search) {
|
|
1790
1600
|
const [vec, kw] = await Promise.all([
|
|
1791
1601
|
this._d.search.search(query, { ...options, codeK, gitK }),
|
|
1792
|
-
Promise.resolve(this._d.bm25
|
|
1602
|
+
Promise.resolve(this._d.bm25?.search(query, { codeK, gitK }) ?? [])
|
|
1793
1603
|
]);
|
|
1794
1604
|
resultLists.push(vec, kw);
|
|
1795
1605
|
}
|
|
1796
1606
|
if (this._d.registry.has("docs")) {
|
|
1797
|
-
const docs = await this.
|
|
1607
|
+
const docs = await this._searchDocs(query, { k: docsK });
|
|
1798
1608
|
if (docs.length > 0) resultLists.push(docs);
|
|
1799
1609
|
}
|
|
1800
1610
|
await this._searchKvCollections(query, cols, resultLists);
|
|
1801
1611
|
if (resultLists.length === 0) return [];
|
|
1802
1612
|
const fused = reciprocalRankFusion(resultLists);
|
|
1803
|
-
return this.
|
|
1613
|
+
return this._rerankResults(query, fused);
|
|
1804
1614
|
}
|
|
1805
1615
|
/** Search non-reserved KV collections and push results. */
|
|
1806
1616
|
async _searchKvCollections(query, cols, resultLists) {
|
|
@@ -1819,10 +1629,16 @@ var SearchAPI = class {
|
|
|
1819
1629
|
}
|
|
1820
1630
|
}
|
|
1821
1631
|
/** Apply reranking if a reranker is configured. */
|
|
1822
|
-
async
|
|
1632
|
+
async _rerankResults(query, fused) {
|
|
1823
1633
|
if (!this._d.config.reranker || fused.length <= 1) return fused;
|
|
1824
1634
|
return rerank(query, fused, this._d.config.reranker);
|
|
1825
1635
|
}
|
|
1636
|
+
/** Search docs directly via the plugin — no circular callback. */
|
|
1637
|
+
async _searchDocs(query, options) {
|
|
1638
|
+
const plugin = this._d.getDocsPlugin();
|
|
1639
|
+
if (!plugin) return [];
|
|
1640
|
+
return plugin.search(query, options);
|
|
1641
|
+
}
|
|
1826
1642
|
// ── Keyword ─────────────────────────────────────
|
|
1827
1643
|
async searchBM25(query, options) {
|
|
1828
1644
|
return this._d.bm25?.search(query, options) ?? [];
|
|
@@ -1838,7 +1654,7 @@ var SearchAPI = class {
|
|
|
1838
1654
|
if (core) sections.push(core);
|
|
1839
1655
|
}
|
|
1840
1656
|
if (this._d.registry.has("docs")) {
|
|
1841
|
-
const docs = await this.
|
|
1657
|
+
const docs = await this._searchDocs(task, { k: options.codeResults ?? 4 });
|
|
1842
1658
|
if (docs.length > 0) {
|
|
1843
1659
|
const body = docs.map((r) => {
|
|
1844
1660
|
const m = r.metadata;
|
|
@@ -1870,7 +1686,7 @@ function isCollectionPlugin(i) {
|
|
|
1870
1686
|
}
|
|
1871
1687
|
__name(isCollectionPlugin, "isCollectionPlugin");
|
|
1872
1688
|
|
|
1873
|
-
// src/
|
|
1689
|
+
// src/api/index-api.ts
|
|
1874
1690
|
var IndexAPI = class {
|
|
1875
1691
|
constructor(_d) {
|
|
1876
1692
|
this._d = _d;
|
|
@@ -1953,6 +1769,166 @@ var IndexAPI = class {
|
|
|
1953
1769
|
}
|
|
1954
1770
|
};
|
|
1955
1771
|
|
|
1772
|
+
// src/services/reembed.ts
|
|
1773
|
+
var TABLES = [
|
|
1774
|
+
{
|
|
1775
|
+
name: "code",
|
|
1776
|
+
textTable: "code_chunks",
|
|
1777
|
+
vectorTable: "code_vectors",
|
|
1778
|
+
idColumn: "id",
|
|
1779
|
+
fkColumn: "chunk_id",
|
|
1780
|
+
textBuilder: /* @__PURE__ */ __name((r) => [
|
|
1781
|
+
`File: ${r.file_path}`,
|
|
1782
|
+
r.name ? `${r.chunk_type}: ${r.name}` : r.chunk_type,
|
|
1783
|
+
r.content
|
|
1784
|
+
].join("\n"), "textBuilder")
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
name: "git",
|
|
1788
|
+
textTable: "git_commits",
|
|
1789
|
+
vectorTable: "git_vectors",
|
|
1790
|
+
idColumn: "id",
|
|
1791
|
+
fkColumn: "commit_id",
|
|
1792
|
+
// Must match git-engine.ts:119-125 exactly
|
|
1793
|
+
textBuilder: /* @__PURE__ */ __name((r) => [
|
|
1794
|
+
`Commit: ${r.message}`,
|
|
1795
|
+
`Author: ${r.author}`,
|
|
1796
|
+
`Date: ${r.date}`,
|
|
1797
|
+
r.files_json && r.files_json !== "[]" ? `Files: ${JSON.parse(r.files_json).join(", ")}` : "",
|
|
1798
|
+
r.diff ? `Changes:
|
|
1799
|
+
${r.diff.slice(0, 2e3)}` : ""
|
|
1800
|
+
].filter(Boolean).join("\n"), "textBuilder")
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
name: "memory",
|
|
1804
|
+
textTable: "memory_patterns",
|
|
1805
|
+
vectorTable: "memory_vectors",
|
|
1806
|
+
idColumn: "id",
|
|
1807
|
+
fkColumn: "pattern_id",
|
|
1808
|
+
// Must match memory/pattern-store.ts:49 exactly
|
|
1809
|
+
textBuilder: /* @__PURE__ */ __name((r) => `${r.task_type} ${r.task} ${r.approach}`, "textBuilder")
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
name: "notes",
|
|
1813
|
+
textTable: "note_memories",
|
|
1814
|
+
vectorTable: "note_vectors",
|
|
1815
|
+
idColumn: "id",
|
|
1816
|
+
fkColumn: "note_id",
|
|
1817
|
+
// Must match notes/engine.ts:90 exactly
|
|
1818
|
+
textBuilder: /* @__PURE__ */ __name((r) => {
|
|
1819
|
+
const decisions = JSON.parse(r.decisions_json || "[]").join(". ");
|
|
1820
|
+
const patterns = JSON.parse(r.patterns_json || "[]").join(". ");
|
|
1821
|
+
return `${r.title}
|
|
1822
|
+
${r.summary}
|
|
1823
|
+
${decisions}
|
|
1824
|
+
${patterns}`;
|
|
1825
|
+
}, "textBuilder")
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
name: "docs",
|
|
1829
|
+
textTable: "doc_chunks",
|
|
1830
|
+
vectorTable: "doc_vectors",
|
|
1831
|
+
idColumn: "id",
|
|
1832
|
+
fkColumn: "chunk_id",
|
|
1833
|
+
// Must match docs-engine.ts:160 exactly
|
|
1834
|
+
textBuilder: /* @__PURE__ */ __name((r) => `title: ${r.title ?? ""} | text: ${r.content}`, "textBuilder")
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
name: "kv",
|
|
1838
|
+
textTable: "kv_data",
|
|
1839
|
+
vectorTable: "kv_vectors",
|
|
1840
|
+
idColumn: "id",
|
|
1841
|
+
fkColumn: "data_id",
|
|
1842
|
+
textBuilder: /* @__PURE__ */ __name((r) => r.content, "textBuilder")
|
|
1843
|
+
}
|
|
1844
|
+
];
|
|
1845
|
+
async function reembedAll(db, embedding, hnswMap, options = {}) {
|
|
1846
|
+
const { batchSize = 50, onProgress } = options;
|
|
1847
|
+
const result = {};
|
|
1848
|
+
let total = 0;
|
|
1849
|
+
for (const table of TABLES) {
|
|
1850
|
+
const count = await reembedTable(db, embedding, table, batchSize, onProgress);
|
|
1851
|
+
result[table.name] = count;
|
|
1852
|
+
total += count;
|
|
1853
|
+
const entry = hnswMap.get(table.name);
|
|
1854
|
+
if (entry && count > 0) {
|
|
1855
|
+
await rebuildHnsw(db, table, entry.hnsw, entry.vecs);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const meta = {
|
|
1859
|
+
provider: embedding.constructor?.name ?? "unknown",
|
|
1860
|
+
dims: String(embedding.dims),
|
|
1861
|
+
reembedded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1862
|
+
};
|
|
1863
|
+
const upsert = db.prepare(
|
|
1864
|
+
"INSERT OR REPLACE INTO embedding_meta (key, value) VALUES (?, ?)"
|
|
1865
|
+
);
|
|
1866
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
1867
|
+
upsert.run(k, v);
|
|
1868
|
+
}
|
|
1869
|
+
return {
|
|
1870
|
+
code: result.code ?? 0,
|
|
1871
|
+
git: result.git ?? 0,
|
|
1872
|
+
memory: result.memory ?? 0,
|
|
1873
|
+
notes: result.notes ?? 0,
|
|
1874
|
+
docs: result.docs ?? 0,
|
|
1875
|
+
kv: result.kv ?? 0,
|
|
1876
|
+
total
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
__name(reembedAll, "reembedAll");
|
|
1880
|
+
async function reembedTable(db, embedding, table, batchSize, onProgress) {
|
|
1881
|
+
const totalCount = db.prepare(
|
|
1882
|
+
`SELECT COUNT(*) as c FROM ${table.textTable}`
|
|
1883
|
+
).get().c;
|
|
1884
|
+
if (totalCount === 0) return 0;
|
|
1885
|
+
const tempTable = `_reembed_${table.vectorTable}`;
|
|
1886
|
+
db.exec(`DROP TABLE IF EXISTS ${tempTable}`);
|
|
1887
|
+
db.exec(`CREATE TABLE ${tempTable} AS SELECT * FROM ${table.vectorTable} WHERE 0`);
|
|
1888
|
+
const insertTemp = db.prepare(
|
|
1889
|
+
`INSERT INTO ${tempTable} (${table.fkColumn}, embedding) VALUES (?, ?)`
|
|
1890
|
+
);
|
|
1891
|
+
let processed = 0;
|
|
1892
|
+
try {
|
|
1893
|
+
for (let offset = 0; offset < totalCount; offset += batchSize) {
|
|
1894
|
+
const batch = db.prepare(
|
|
1895
|
+
`SELECT * FROM ${table.textTable} LIMIT ? OFFSET ?`
|
|
1896
|
+
).all(batchSize, offset);
|
|
1897
|
+
const texts = batch.map((r) => table.textBuilder(r));
|
|
1898
|
+
const vectors = await embedding.embedBatch(texts);
|
|
1899
|
+
db.transaction(() => {
|
|
1900
|
+
for (let j = 0; j < batch.length; j++) {
|
|
1901
|
+
insertTemp.run(batch[j][table.idColumn], vecToBuffer(vectors[j]));
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
processed += batch.length;
|
|
1905
|
+
onProgress?.(table.name, processed, totalCount);
|
|
1906
|
+
}
|
|
1907
|
+
db.transaction(() => {
|
|
1908
|
+
db.exec(`DELETE FROM ${table.vectorTable}`);
|
|
1909
|
+
db.exec(`INSERT INTO ${table.vectorTable} SELECT * FROM ${tempTable}`);
|
|
1910
|
+
});
|
|
1911
|
+
} finally {
|
|
1912
|
+
db.exec(`DROP TABLE IF EXISTS ${tempTable}`);
|
|
1913
|
+
}
|
|
1914
|
+
return processed;
|
|
1915
|
+
}
|
|
1916
|
+
__name(reembedTable, "reembedTable");
|
|
1917
|
+
async function rebuildHnsw(db, table, hnsw, vecs) {
|
|
1918
|
+
vecs.clear();
|
|
1919
|
+
hnsw.reinit();
|
|
1920
|
+
const rows = db.prepare(
|
|
1921
|
+
`SELECT ${table.fkColumn} as id, embedding FROM ${table.vectorTable}`
|
|
1922
|
+
).all();
|
|
1923
|
+
for (const row of rows) {
|
|
1924
|
+
const buf = Buffer.from(row.embedding);
|
|
1925
|
+
const vec = new Float32Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
1926
|
+
hnsw.add(vec, row.id);
|
|
1927
|
+
vecs.set(row.id, vec);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
__name(rebuildHnsw, "rebuildHnsw");
|
|
1931
|
+
|
|
1956
1932
|
// src/services/watch.ts
|
|
1957
1933
|
import * as fs2 from "fs";
|
|
1958
1934
|
import * as path3 from "path";
|
|
@@ -1973,7 +1949,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
1973
1949
|
customPatterns.push({ indexer, patterns: indexer.watchPatterns() });
|
|
1974
1950
|
}
|
|
1975
1951
|
}
|
|
1976
|
-
function
|
|
1952
|
+
function matchCustomPlugin(filePath) {
|
|
1977
1953
|
const rel = path3.relative(repoPath, filePath);
|
|
1978
1954
|
for (const { indexer, patterns } of customPatterns) {
|
|
1979
1955
|
for (const pattern of patterns) {
|
|
@@ -1982,7 +1958,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
1982
1958
|
}
|
|
1983
1959
|
return null;
|
|
1984
1960
|
}
|
|
1985
|
-
__name(
|
|
1961
|
+
__name(matchCustomPlugin, "matchCustomPlugin");
|
|
1986
1962
|
function matchGlob(filePath, pattern) {
|
|
1987
1963
|
if (pattern.startsWith("**/")) {
|
|
1988
1964
|
const suffix = pattern.slice(3);
|
|
@@ -1997,7 +1973,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
1997
1973
|
}
|
|
1998
1974
|
__name(matchGlob, "matchGlob");
|
|
1999
1975
|
let flushing = false;
|
|
2000
|
-
async function
|
|
1976
|
+
async function processPending() {
|
|
2001
1977
|
if (flushing || pending.size === 0) return;
|
|
2002
1978
|
flushing = true;
|
|
2003
1979
|
try {
|
|
@@ -2006,7 +1982,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
2006
1982
|
let needsReindex = false;
|
|
2007
1983
|
for (const filePath of files) {
|
|
2008
1984
|
const absPath = path3.resolve(repoPath, filePath);
|
|
2009
|
-
const customIndexer =
|
|
1985
|
+
const customIndexer = matchCustomPlugin(absPath);
|
|
2010
1986
|
if (customIndexer && isWatchable(customIndexer)) {
|
|
2011
1987
|
try {
|
|
2012
1988
|
const handled = await customIndexer.onFileChange(absPath, detectEvent(absPath));
|
|
@@ -2033,11 +2009,11 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
2033
2009
|
} finally {
|
|
2034
2010
|
flushing = false;
|
|
2035
2011
|
if (pending.size > 0) {
|
|
2036
|
-
timer = setTimeout(() =>
|
|
2012
|
+
timer = setTimeout(() => processPending(), debounceMs);
|
|
2037
2013
|
}
|
|
2038
2014
|
}
|
|
2039
2015
|
}
|
|
2040
|
-
__name(
|
|
2016
|
+
__name(processPending, "processPending");
|
|
2041
2017
|
function detectEvent(filePath) {
|
|
2042
2018
|
try {
|
|
2043
2019
|
fs2.accessSync(filePath);
|
|
@@ -2055,7 +2031,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
2055
2031
|
}
|
|
2056
2032
|
if (isIgnoredFile(path3.basename(filename))) return false;
|
|
2057
2033
|
if (isSupported(filename)) return true;
|
|
2058
|
-
if (
|
|
2034
|
+
if (matchCustomPlugin(path3.resolve(repoPath, filename))) return true;
|
|
2059
2035
|
return false;
|
|
2060
2036
|
}
|
|
2061
2037
|
__name(shouldWatch, "shouldWatch");
|
|
@@ -2068,7 +2044,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
2068
2044
|
if (!shouldWatch(filename)) return;
|
|
2069
2045
|
pending.add(filename);
|
|
2070
2046
|
if (timer) clearTimeout(timer);
|
|
2071
|
-
timer = setTimeout(() =>
|
|
2047
|
+
timer = setTimeout(() => processPending(), debounceMs);
|
|
2072
2048
|
});
|
|
2073
2049
|
watcher.on("error", (err) => {
|
|
2074
2050
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -2101,7 +2077,7 @@ var BrainBank = class extends EventEmitter {
|
|
|
2101
2077
|
_config;
|
|
2102
2078
|
_db;
|
|
2103
2079
|
_embedding;
|
|
2104
|
-
_registry = new
|
|
2080
|
+
_registry = new PluginRegistry();
|
|
2105
2081
|
_searchAPI;
|
|
2106
2082
|
_indexAPI;
|
|
2107
2083
|
_initialized = false;
|
|
@@ -2117,28 +2093,28 @@ var BrainBank = class extends EventEmitter {
|
|
|
2117
2093
|
super();
|
|
2118
2094
|
this._config = resolveConfig(config);
|
|
2119
2095
|
}
|
|
2120
|
-
// ──
|
|
2096
|
+
// ── Plugin registration ──────────────────────────
|
|
2121
2097
|
/**
|
|
2122
|
-
* Register
|
|
2098
|
+
* Register a plugin. Chainable.
|
|
2123
2099
|
*
|
|
2124
2100
|
* brain.use(code({ repoPath: '.' })).use(docs());
|
|
2125
2101
|
*/
|
|
2126
|
-
use(
|
|
2102
|
+
use(plugin) {
|
|
2127
2103
|
if (this._initialized)
|
|
2128
|
-
throw new Error(`BrainBank: Cannot add
|
|
2129
|
-
this._registry.register(
|
|
2104
|
+
throw new Error(`BrainBank: Cannot add plugin '${plugin.name}' after initialization. Call .use() before any operations.`);
|
|
2105
|
+
this._registry.register(plugin);
|
|
2130
2106
|
return this;
|
|
2131
2107
|
}
|
|
2132
|
-
/** Get the list of registered
|
|
2133
|
-
get
|
|
2108
|
+
/** Get the list of registered plugin names. */
|
|
2109
|
+
get plugins() {
|
|
2134
2110
|
return this._registry.names;
|
|
2135
2111
|
}
|
|
2136
|
-
/** Check if
|
|
2112
|
+
/** Check if a plugin is loaded. Also matches type prefix (e.g. 'code' matches 'code:frontend'). */
|
|
2137
2113
|
has(name) {
|
|
2138
2114
|
return this._registry.has(name);
|
|
2139
2115
|
}
|
|
2140
|
-
/** Get
|
|
2141
|
-
|
|
2116
|
+
/** Get a plugin instance. Throws if not loaded. */
|
|
2117
|
+
plugin(n) {
|
|
2142
2118
|
return this._registry.get(n);
|
|
2143
2119
|
}
|
|
2144
2120
|
// ── Initialization ───────────────────────────────
|
|
@@ -2176,13 +2152,13 @@ var BrainBank = class extends EventEmitter {
|
|
|
2176
2152
|
}
|
|
2177
2153
|
async _runInitialize(options = {}) {
|
|
2178
2154
|
if (this._initialized) return;
|
|
2179
|
-
const
|
|
2155
|
+
const initializer = new Initializer(this._config, (e, d) => this.emit(e, d));
|
|
2156
|
+
const early = await initializer.early(options);
|
|
2180
2157
|
this._db = early.db;
|
|
2181
2158
|
this._embedding = early.embedding;
|
|
2182
2159
|
this._kvHnsw = early.kvHnsw;
|
|
2183
|
-
const late = await
|
|
2160
|
+
const late = await initializer.late(
|
|
2184
2161
|
early,
|
|
2185
|
-
this._config,
|
|
2186
2162
|
this._registry,
|
|
2187
2163
|
this._sharedHnsw,
|
|
2188
2164
|
this._kvVecs,
|
|
@@ -2192,7 +2168,10 @@ var BrainBank = class extends EventEmitter {
|
|
|
2192
2168
|
...late,
|
|
2193
2169
|
registry: this._registry,
|
|
2194
2170
|
config: this._config,
|
|
2195
|
-
|
|
2171
|
+
getDocsPlugin: /* @__PURE__ */ __name(() => {
|
|
2172
|
+
const docs = this._registry.get("docs");
|
|
2173
|
+
return docs && isCollectionPlugin(docs) ? docs : void 0;
|
|
2174
|
+
}, "getDocsPlugin"),
|
|
2196
2175
|
collection: /* @__PURE__ */ __name((n) => this.collection(n), "collection")
|
|
2197
2176
|
});
|
|
2198
2177
|
this._indexAPI = new IndexAPI({
|
|
@@ -2201,7 +2180,7 @@ var BrainBank = class extends EventEmitter {
|
|
|
2201
2180
|
emit: /* @__PURE__ */ __name((e, d) => this.emit(e, d), "emit")
|
|
2202
2181
|
});
|
|
2203
2182
|
this._initialized = true;
|
|
2204
|
-
this.emit("initialized", {
|
|
2183
|
+
this.emit("initialized", { plugins: this.plugins });
|
|
2205
2184
|
}
|
|
2206
2185
|
// ── Collections (KV) ────────────────────────────
|
|
2207
2186
|
/**
|
|
@@ -2336,13 +2315,13 @@ var BrainBank = class extends EventEmitter {
|
|
|
2336
2315
|
/** Get git history for a specific file. */
|
|
2337
2316
|
async fileHistory(filePath, limit = 20) {
|
|
2338
2317
|
await this.initialize();
|
|
2339
|
-
const gitPlugin = this.
|
|
2318
|
+
const gitPlugin = this.plugin("git");
|
|
2340
2319
|
return gitPlugin.fileHistory(filePath, limit);
|
|
2341
2320
|
}
|
|
2342
2321
|
/** Get co-edit suggestions for a file. */
|
|
2343
2322
|
coEdits(filePath, limit = 5) {
|
|
2344
2323
|
this._requireInit("coEdits");
|
|
2345
|
-
const gitPlugin = this.
|
|
2324
|
+
const gitPlugin = this.plugin("git");
|
|
2346
2325
|
return gitPlugin.suggestCoEdits(filePath, limit);
|
|
2347
2326
|
}
|
|
2348
2327
|
// ── Stats ────────────────────────────────────────
|
|
@@ -2444,11 +2423,10 @@ export {
|
|
|
2444
2423
|
resolveConfig,
|
|
2445
2424
|
Collection,
|
|
2446
2425
|
HNSWIndex,
|
|
2447
|
-
LocalEmbedding,
|
|
2448
2426
|
searchMMR,
|
|
2449
2427
|
VectorSearch,
|
|
2450
2428
|
KeywordSearch,
|
|
2451
2429
|
ContextBuilder,
|
|
2452
2430
|
BrainBank
|
|
2453
2431
|
};
|
|
2454
|
-
//# sourceMappingURL=chunk-
|
|
2432
|
+
//# sourceMappingURL=chunk-DI3H6JVZ.js.map
|