brainbank 0.3.1 → 0.5.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 +174 -15
- package/assets/architecture.png +0 -0
- package/dist/{base-4SUgeRWT.d.ts → base-DZWtdgIf.d.ts} +23 -27
- package/dist/chunk-6XOXM7MI.js +136 -0
- package/dist/chunk-6XOXM7MI.js.map +1 -0
- package/dist/{chunk-FINIFKAY.js → chunk-BNV43SEF.js} +5 -4
- package/dist/chunk-BNV43SEF.js.map +1 -0
- package/dist/{chunk-MGIFEPYZ.js → chunk-DDECTPRM.js} +22 -17
- package/dist/chunk-DDECTPRM.js.map +1 -0
- package/dist/{chunk-5VUYPNH3.js → chunk-HNPABX7L.js} +6 -3
- package/dist/chunk-HNPABX7L.js.map +1 -0
- package/dist/{chunk-2BEWWQL2.js → chunk-MY36UPPQ.js} +227 -112
- package/dist/chunk-MY36UPPQ.js.map +1 -0
- package/dist/chunk-N2OJRXSB.js +117 -0
- package/dist/chunk-N2OJRXSB.js.map +1 -0
- package/dist/{chunk-FI7GWG4W.js → chunk-TTXVJFAE.js} +5 -2
- package/dist/chunk-TTXVJFAE.js.map +1 -0
- package/dist/{chunk-QNHBCOKB.js → chunk-U2Q2XGPZ.js} +7 -2
- package/dist/{chunk-QNHBCOKB.js.map → chunk-U2Q2XGPZ.js.map} +1 -1
- package/dist/{chunk-E6WQM4DN.js → chunk-YOLKSYWK.js} +1 -1
- package/dist/chunk-YOLKSYWK.js.map +1 -0
- package/dist/{chunk-Y3JKI6QN.js → chunk-YRGUIRN5.js} +234 -57
- package/dist/chunk-YRGUIRN5.js.map +1 -0
- package/dist/cli.js +21 -10
- package/dist/cli.js.map +1 -1
- package/dist/code.d.ts +1 -1
- package/dist/code.js +2 -1
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +2 -1
- package/dist/git.d.ts +1 -1
- package/dist/git.js +2 -1
- package/dist/index.d.ts +100 -4
- package/dist/index.js +16 -8
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +1 -1
- package/dist/memory.js +2 -2
- package/dist/notes.d.ts +1 -1
- package/dist/notes.js +3 -2
- package/dist/perplexity-context-embedding-KSVSZXMD.js +9 -0
- package/dist/perplexity-context-embedding-KSVSZXMD.js.map +1 -0
- package/dist/perplexity-embedding-227WQY4R.js +10 -0
- package/dist/perplexity-embedding-227WQY4R.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2BEWWQL2.js.map +0 -1
- package/dist/chunk-5VUYPNH3.js.map +0 -1
- package/dist/chunk-E6WQM4DN.js.map +0 -1
- package/dist/chunk-FI7GWG4W.js.map +0 -1
- package/dist/chunk-FINIFKAY.js.map +0 -1
- package/dist/chunk-MGIFEPYZ.js.map +0 -1
- package/dist/chunk-Y3JKI6QN.js.map +0 -1
|
@@ -2,15 +2,19 @@ import {
|
|
|
2
2
|
isIgnoredDir,
|
|
3
3
|
isIgnoredFile,
|
|
4
4
|
isSupported
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-DDECTPRM.js";
|
|
6
|
+
import {
|
|
7
|
+
rerank
|
|
8
|
+
} from "./chunk-YRGUIRN5.js";
|
|
6
9
|
import {
|
|
7
10
|
normalizeBM25,
|
|
8
11
|
reciprocalRankFusion,
|
|
9
12
|
sanitizeFTS
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YOLKSYWK.js";
|
|
11
14
|
import {
|
|
12
|
-
cosineSimilarity
|
|
13
|
-
|
|
15
|
+
cosineSimilarity,
|
|
16
|
+
vecToBuffer
|
|
17
|
+
} from "./chunk-U2Q2XGPZ.js";
|
|
14
18
|
import {
|
|
15
19
|
__name
|
|
16
20
|
} from "./chunk-7QVYU63E.js";
|
|
@@ -81,7 +85,7 @@ var Collection = class {
|
|
|
81
85
|
const id = Number(result.lastInsertRowid);
|
|
82
86
|
this._db.prepare(
|
|
83
87
|
"INSERT INTO kv_vectors (data_id, embedding) VALUES (?, ?)"
|
|
84
|
-
).run(id,
|
|
88
|
+
).run(id, vecToBuffer(vec));
|
|
85
89
|
this._hnsw.add(vec, id);
|
|
86
90
|
this._vecs.set(id, vec);
|
|
87
91
|
return id;
|
|
@@ -110,7 +114,7 @@ var Collection = class {
|
|
|
110
114
|
expiresAt
|
|
111
115
|
);
|
|
112
116
|
const id = Number(result.lastInsertRowid);
|
|
113
|
-
insertVec.run(id,
|
|
117
|
+
insertVec.run(id, vecToBuffer(vecs[i]));
|
|
114
118
|
ids.push(id);
|
|
115
119
|
}
|
|
116
120
|
});
|
|
@@ -146,12 +150,15 @@ var Collection = class {
|
|
|
146
150
|
if (results.length >= k) break;
|
|
147
151
|
}
|
|
148
152
|
if (this._reranker && results.length > 1) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
const asSearchResults = results.map((r) => ({
|
|
154
|
+
type: "collection",
|
|
155
|
+
score: r.score ?? 0,
|
|
156
|
+
content: r.content,
|
|
157
|
+
metadata: { id: r.id }
|
|
154
158
|
}));
|
|
159
|
+
const reranked = await rerank(query, asSearchResults, this._reranker);
|
|
160
|
+
const rerankedById = new Map(reranked.map((r) => [r.metadata?.id, r.score]));
|
|
161
|
+
const blended = results.map((r) => ({ ...r, score: rerankedById.get(r.id) ?? r.score ?? 0 }));
|
|
155
162
|
return this._filterByTags(
|
|
156
163
|
blended.sort((a, b) => (b.score ?? 0) - (a.score ?? 0)),
|
|
157
164
|
tags
|
|
@@ -216,19 +223,14 @@ var Collection = class {
|
|
|
216
223
|
}
|
|
217
224
|
// ── Private ──────────────────────────────────────
|
|
218
225
|
_removeById(id) {
|
|
219
|
-
this._vecs.delete(id);
|
|
220
|
-
this._hnsw.remove(id);
|
|
221
226
|
this._db.prepare("DELETE FROM kv_data WHERE id = ?").run(id);
|
|
227
|
+
this._hnsw.remove(id);
|
|
228
|
+
this._vecs.delete(id);
|
|
222
229
|
}
|
|
223
230
|
async _searchVector(query, k, minScore) {
|
|
224
231
|
if (this._hnsw.size === 0) return [];
|
|
225
232
|
const queryVec = await this._embedding.embed(query);
|
|
226
|
-
const
|
|
227
|
-
const collectionCount = this._db.prepare(
|
|
228
|
-
"SELECT COUNT(*) as c FROM kv_data WHERE collection = ? AND (expires_at IS NULL OR expires_at > ?)"
|
|
229
|
-
).get(this._name, now)?.c ?? 0;
|
|
230
|
-
const ratio = collectionCount > 0 ? Math.max(3, Math.min(50, Math.ceil(this._hnsw.size / collectionCount))) : 3;
|
|
231
|
-
const searchK = Math.min(k * ratio, this._hnsw.size);
|
|
233
|
+
const searchK = Math.min(k * 10, this._hnsw.size);
|
|
232
234
|
const hits = this._hnsw.search(queryVec, searchK);
|
|
233
235
|
const ids = hits.map((h) => h.id);
|
|
234
236
|
if (ids.length === 0) return [];
|
|
@@ -308,6 +310,7 @@ function parseDuration(s) {
|
|
|
308
310
|
__name(parseDuration, "parseDuration");
|
|
309
311
|
|
|
310
312
|
// src/providers/vector/hnsw-index.ts
|
|
313
|
+
import { existsSync } from "fs";
|
|
311
314
|
var HNSWIndex = class {
|
|
312
315
|
constructor(_dims, _maxElements = 2e6, _M = 16, _efConstruction = 200, _efSearch = 50) {
|
|
313
316
|
this._dims = _dims;
|
|
@@ -398,6 +401,38 @@ var HNSWIndex = class {
|
|
|
398
401
|
get size() {
|
|
399
402
|
return this._ids.size;
|
|
400
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Save the HNSW graph to disk.
|
|
406
|
+
* The file can be loaded later with tryLoad() to skip vector-by-vector insertion.
|
|
407
|
+
*/
|
|
408
|
+
save(path4) {
|
|
409
|
+
if (!this._index || this._ids.size === 0) return;
|
|
410
|
+
this._index.writeIndexSync(path4);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Try to load a previously saved HNSW index from disk.
|
|
414
|
+
* Returns true if loaded successfully, false if stale or missing.
|
|
415
|
+
* @param path File path to the saved index
|
|
416
|
+
* @param expectedCount Expected number of vectors (from SQLite) — used to detect staleness
|
|
417
|
+
*/
|
|
418
|
+
tryLoad(path4, expectedCount) {
|
|
419
|
+
if (!this._index || !existsSync(path4)) return false;
|
|
420
|
+
try {
|
|
421
|
+
this._index.readIndexSync(path4);
|
|
422
|
+
const loadedCount = this._index.getCurrentCount();
|
|
423
|
+
if (loadedCount !== expectedCount) {
|
|
424
|
+
this.reinit();
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
const ids = this._index.getIdsList();
|
|
428
|
+
this._ids = new Set(ids);
|
|
429
|
+
this._index.setEf(this._efSearch);
|
|
430
|
+
return true;
|
|
431
|
+
} catch {
|
|
432
|
+
this.reinit();
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
401
436
|
};
|
|
402
437
|
|
|
403
438
|
// src/providers/embeddings/local-embedding.ts
|
|
@@ -460,7 +495,7 @@ var LocalEmbedding = class {
|
|
|
460
495
|
const output = await pipe(batch, { pooling: "mean", normalize: true });
|
|
461
496
|
for (let j = 0; j < batch.length; j++) {
|
|
462
497
|
const start = j * this.dims;
|
|
463
|
-
results.push(
|
|
498
|
+
results.push(output.data.slice(start, start + this.dims));
|
|
464
499
|
}
|
|
465
500
|
}
|
|
466
501
|
return results;
|
|
@@ -502,22 +537,6 @@ function searchMMR(index, query, vectorCache, k, lambda = 0.7) {
|
|
|
502
537
|
}
|
|
503
538
|
__name(searchMMR, "searchMMR");
|
|
504
539
|
|
|
505
|
-
// src/search/vector/rerank.ts
|
|
506
|
-
async function rerank(query, results, reranker) {
|
|
507
|
-
const documents = results.map((r) => r.content);
|
|
508
|
-
const scores = await reranker.rank(query, documents);
|
|
509
|
-
const blended = results.map((r, i) => {
|
|
510
|
-
const pos = i + 1;
|
|
511
|
-
const rrfWeight = pos <= 3 ? 0.75 : pos <= 10 ? 0.6 : 0.4;
|
|
512
|
-
return {
|
|
513
|
-
...r,
|
|
514
|
-
score: rrfWeight * r.score + (1 - rrfWeight) * (scores[i] ?? 0)
|
|
515
|
-
};
|
|
516
|
-
});
|
|
517
|
-
return blended.sort((a, b) => b.score - a.score);
|
|
518
|
-
}
|
|
519
|
-
__name(rerank, "rerank");
|
|
520
|
-
|
|
521
540
|
// src/search/vector/vector-search.ts
|
|
522
541
|
var VectorSearch = class {
|
|
523
542
|
static {
|
|
@@ -645,6 +664,10 @@ var VectorSearch = class {
|
|
|
645
664
|
};
|
|
646
665
|
|
|
647
666
|
// src/search/keyword/keyword-search.ts
|
|
667
|
+
function isFTSError(e) {
|
|
668
|
+
return e instanceof Error && /fts5|syntax error|parse error/i.test(e.message);
|
|
669
|
+
}
|
|
670
|
+
__name(isFTSError, "isFTSError");
|
|
648
671
|
var KeywordSearch = class {
|
|
649
672
|
constructor(_db) {
|
|
650
673
|
this._db = _db;
|
|
@@ -683,7 +706,8 @@ var KeywordSearch = class {
|
|
|
683
706
|
seenIds.add(r.id);
|
|
684
707
|
results.push(this._toCodeResult(r, normalizeBM25(r.score), "bm25"));
|
|
685
708
|
}
|
|
686
|
-
} catch {
|
|
709
|
+
} catch (e) {
|
|
710
|
+
if (!isFTSError(e)) throw e;
|
|
687
711
|
}
|
|
688
712
|
this._searchCodeByPath(rawQuery, seenIds, results);
|
|
689
713
|
}
|
|
@@ -704,7 +728,8 @@ var KeywordSearch = class {
|
|
|
704
728
|
results.push(this._toCodeResult(r, 0.6, "bm25-path"));
|
|
705
729
|
}
|
|
706
730
|
}
|
|
707
|
-
} catch {
|
|
731
|
+
} catch (e) {
|
|
732
|
+
if (!isFTSError(e)) throw e;
|
|
708
733
|
}
|
|
709
734
|
}
|
|
710
735
|
/** FTS5 search across git commits. */
|
|
@@ -738,7 +763,8 @@ var KeywordSearch = class {
|
|
|
738
763
|
}
|
|
739
764
|
});
|
|
740
765
|
}
|
|
741
|
-
} catch {
|
|
766
|
+
} catch (e) {
|
|
767
|
+
if (!isFTSError(e)) throw e;
|
|
742
768
|
}
|
|
743
769
|
}
|
|
744
770
|
/** FTS5 search across memory patterns. */
|
|
@@ -769,7 +795,8 @@ var KeywordSearch = class {
|
|
|
769
795
|
}
|
|
770
796
|
});
|
|
771
797
|
}
|
|
772
|
-
} catch {
|
|
798
|
+
} catch (e) {
|
|
799
|
+
if (!isFTSError(e)) throw e;
|
|
773
800
|
}
|
|
774
801
|
}
|
|
775
802
|
/** Map a code_chunks row to a CodeResult. */
|
|
@@ -1002,6 +1029,9 @@ var IndexerRegistry = class {
|
|
|
1002
1029
|
}
|
|
1003
1030
|
};
|
|
1004
1031
|
|
|
1032
|
+
// src/core/initializer.ts
|
|
1033
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1034
|
+
|
|
1005
1035
|
// src/db/database.ts
|
|
1006
1036
|
import BetterSqlite3 from "better-sqlite3";
|
|
1007
1037
|
import * as fs from "fs";
|
|
@@ -1484,24 +1514,34 @@ async function reembedTable(db, embedding, table, batchSize, onProgress) {
|
|
|
1484
1514
|
`SELECT COUNT(*) as c FROM ${table.textTable}`
|
|
1485
1515
|
).get().c;
|
|
1486
1516
|
if (totalCount === 0) return 0;
|
|
1487
|
-
const
|
|
1488
|
-
|
|
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 (?, ?)`
|
|
1489
1522
|
);
|
|
1490
|
-
db.prepare(`DELETE FROM ${table.vectorTable}`).run();
|
|
1491
1523
|
let processed = 0;
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
+
}
|
|
1498
1539
|
db.transaction(() => {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
}
|
|
1540
|
+
db.exec(`DELETE FROM ${table.vectorTable}`);
|
|
1541
|
+
db.exec(`INSERT INTO ${table.vectorTable} SELECT * FROM ${tempTable}`);
|
|
1502
1542
|
});
|
|
1503
|
-
|
|
1504
|
-
|
|
1543
|
+
} finally {
|
|
1544
|
+
db.exec(`DROP TABLE IF EXISTS ${tempTable}`);
|
|
1505
1545
|
}
|
|
1506
1546
|
return processed;
|
|
1507
1547
|
}
|
|
@@ -1584,12 +1624,19 @@ __name(earlyInit, "earlyInit");
|
|
|
1584
1624
|
async function lateInit(early, config, registry, sharedHnsw, kvVecs, getCollection) {
|
|
1585
1625
|
const { db, embedding, kvHnsw, skipVectorLoad } = early;
|
|
1586
1626
|
if (!skipVectorLoad) {
|
|
1587
|
-
|
|
1627
|
+
const kvIndexPath = hnswPath(config.dbPath, "kv");
|
|
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);
|
|
1633
|
+
}
|
|
1588
1634
|
}
|
|
1589
1635
|
const ctx = buildIndexerContext(db, embedding, config, sharedHnsw, skipVectorLoad, getCollection);
|
|
1590
1636
|
for (const mod of registry.all) {
|
|
1591
1637
|
await mod.initialize(ctx);
|
|
1592
1638
|
}
|
|
1639
|
+
saveAllHnsw(config.dbPath, kvHnsw, sharedHnsw);
|
|
1593
1640
|
return buildSearchLayer(db, embedding, config, registry, sharedHnsw);
|
|
1594
1641
|
}
|
|
1595
1642
|
__name(lateInit, "lateInit");
|
|
@@ -1607,7 +1654,14 @@ function buildIndexerContext(db, embedding, config, sharedHnsw, skipVectorLoad,
|
|
|
1607
1654
|
).init(), "createHnsw"),
|
|
1608
1655
|
loadVectors: /* @__PURE__ */ __name((table, idCol, hnsw, cache) => {
|
|
1609
1656
|
if (skipVectorLoad) return;
|
|
1610
|
-
|
|
1657
|
+
const indexName = table.replace("_vectors", "").replace("_chunks", "");
|
|
1658
|
+
const indexPath = hnswPath(config.dbPath, indexName);
|
|
1659
|
+
const rowCount = countRows(db, table);
|
|
1660
|
+
if (hnsw.tryLoad(indexPath, rowCount)) {
|
|
1661
|
+
loadVecCache(db, table, idCol, cache);
|
|
1662
|
+
} else {
|
|
1663
|
+
loadVectors(db, table, idCol, hnsw, cache);
|
|
1664
|
+
}
|
|
1611
1665
|
}, "loadVectors"),
|
|
1612
1666
|
getOrCreateSharedHnsw: /* @__PURE__ */ __name(async (type, maxElements) => {
|
|
1613
1667
|
const existing = sharedHnsw.get(type);
|
|
@@ -1649,9 +1703,28 @@ function buildSearchLayer(db, embedding, config, registry, sharedHnsw) {
|
|
|
1649
1703
|
return { search, bm25, contextBuilder };
|
|
1650
1704
|
}
|
|
1651
1705
|
__name(buildSearchLayer, "buildSearchLayer");
|
|
1706
|
+
function hnswPath(dbPath, name) {
|
|
1707
|
+
return join2(dirname2(dbPath), `hnsw-${name}.index`);
|
|
1708
|
+
}
|
|
1709
|
+
__name(hnswPath, "hnswPath");
|
|
1710
|
+
function countRows(db, table) {
|
|
1711
|
+
const row = db.prepare(`SELECT COUNT(*) as c FROM ${table}`).get();
|
|
1712
|
+
return row?.c ?? 0;
|
|
1713
|
+
}
|
|
1714
|
+
__name(countRows, "countRows");
|
|
1715
|
+
function saveAllHnsw(dbPath, kvHnsw, sharedHnsw) {
|
|
1716
|
+
try {
|
|
1717
|
+
kvHnsw.save(hnswPath(dbPath, "kv"));
|
|
1718
|
+
for (const [name, { hnsw }] of sharedHnsw) {
|
|
1719
|
+
hnsw.save(hnswPath(dbPath, name));
|
|
1720
|
+
}
|
|
1721
|
+
} catch {
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
__name(saveAllHnsw, "saveAllHnsw");
|
|
1652
1725
|
function loadVectors(db, table, idCol, hnsw, cache) {
|
|
1653
|
-
const
|
|
1654
|
-
for (const row of
|
|
1726
|
+
const iter = db.prepare(`SELECT ${idCol}, embedding FROM ${table}`).iterate();
|
|
1727
|
+
for (const row of iter) {
|
|
1655
1728
|
const vec = new Float32Array(
|
|
1656
1729
|
row.embedding.buffer.slice(
|
|
1657
1730
|
row.embedding.byteOffset,
|
|
@@ -1663,6 +1736,19 @@ function loadVectors(db, table, idCol, hnsw, cache) {
|
|
|
1663
1736
|
}
|
|
1664
1737
|
}
|
|
1665
1738
|
__name(loadVectors, "loadVectors");
|
|
1739
|
+
function loadVecCache(db, table, idCol, cache) {
|
|
1740
|
+
const iter = db.prepare(`SELECT ${idCol}, embedding FROM ${table}`).iterate();
|
|
1741
|
+
for (const row of iter) {
|
|
1742
|
+
const vec = new Float32Array(
|
|
1743
|
+
row.embedding.buffer.slice(
|
|
1744
|
+
row.embedding.byteOffset,
|
|
1745
|
+
row.embedding.byteOffset + row.embedding.byteLength
|
|
1746
|
+
)
|
|
1747
|
+
);
|
|
1748
|
+
cache.set(row[idCol], vec);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
__name(loadVecCache, "loadVecCache");
|
|
1666
1752
|
|
|
1667
1753
|
// src/core/search-api.ts
|
|
1668
1754
|
var SearchAPI = class {
|
|
@@ -1735,11 +1821,7 @@ var SearchAPI = class {
|
|
|
1735
1821
|
/** Apply reranking if a reranker is configured. */
|
|
1736
1822
|
async _applyReranking(query, fused) {
|
|
1737
1823
|
if (!this._d.config.reranker || fused.length <= 1) return fused;
|
|
1738
|
-
|
|
1739
|
-
return fused.map((r, i) => {
|
|
1740
|
-
const w = i < 3 ? 0.75 : i < 10 ? 0.6 : 0.4;
|
|
1741
|
-
return { ...r, score: w * r.score + (1 - w) * (scores[i] ?? 0) };
|
|
1742
|
-
}).sort((a, b) => b.score - a.score);
|
|
1824
|
+
return rerank(query, fused, this._d.config.reranker);
|
|
1743
1825
|
}
|
|
1744
1826
|
// ── Keyword ─────────────────────────────────────
|
|
1745
1827
|
async searchBM25(query, options) {
|
|
@@ -1774,6 +1856,20 @@ ${body}`);
|
|
|
1774
1856
|
}
|
|
1775
1857
|
};
|
|
1776
1858
|
|
|
1859
|
+
// src/indexers/base.ts
|
|
1860
|
+
function isIndexable(i) {
|
|
1861
|
+
return typeof i.index === "function";
|
|
1862
|
+
}
|
|
1863
|
+
__name(isIndexable, "isIndexable");
|
|
1864
|
+
function isWatchable(i) {
|
|
1865
|
+
return typeof i.onFileChange === "function" && typeof i.watchPatterns === "function";
|
|
1866
|
+
}
|
|
1867
|
+
__name(isWatchable, "isWatchable");
|
|
1868
|
+
function isCollectionPlugin(i) {
|
|
1869
|
+
return typeof i.addCollection === "function" && typeof i.listCollections === "function";
|
|
1870
|
+
}
|
|
1871
|
+
__name(isCollectionPlugin, "isCollectionPlugin");
|
|
1872
|
+
|
|
1777
1873
|
// src/core/index-api.ts
|
|
1778
1874
|
var IndexAPI = class {
|
|
1779
1875
|
constructor(_d) {
|
|
@@ -1787,6 +1883,7 @@ var IndexAPI = class {
|
|
|
1787
1883
|
const result = {};
|
|
1788
1884
|
if (want.has("code")) {
|
|
1789
1885
|
for (const mod of this._d.registry.allByType("code")) {
|
|
1886
|
+
if (!isIndexable(mod)) continue;
|
|
1790
1887
|
const label = mod.name === "code" ? "code" : mod.name;
|
|
1791
1888
|
options.onProgress?.(label, "Starting...");
|
|
1792
1889
|
const r = await mod.index({
|
|
@@ -1804,6 +1901,7 @@ var IndexAPI = class {
|
|
|
1804
1901
|
}
|
|
1805
1902
|
if (want.has("git")) {
|
|
1806
1903
|
for (const mod of this._d.registry.allByType("git")) {
|
|
1904
|
+
if (!isIndexable(mod)) continue;
|
|
1807
1905
|
const label = mod.name === "git" ? "git" : mod.name;
|
|
1808
1906
|
options.onProgress?.(label, "Starting...");
|
|
1809
1907
|
const r = await mod.index({
|
|
@@ -1819,16 +1917,19 @@ var IndexAPI = class {
|
|
|
1819
1917
|
}
|
|
1820
1918
|
}
|
|
1821
1919
|
if (want.has("docs") && this._d.registry.has("docs")) {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1920
|
+
const docsPlugin = this._d.registry.get("docs");
|
|
1921
|
+
if (isCollectionPlugin(docsPlugin)) {
|
|
1922
|
+
options.onProgress?.("docs", "Starting...");
|
|
1923
|
+
result.docs = await docsPlugin.indexCollections({
|
|
1924
|
+
onProgress: /* @__PURE__ */ __name((coll, file, cur, total) => options.onProgress?.("docs", `[${coll}] ${cur}/${total}: ${file}`), "onProgress")
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1826
1927
|
}
|
|
1827
1928
|
this._d.emit("indexed", result);
|
|
1828
1929
|
return result;
|
|
1829
1930
|
}
|
|
1830
1931
|
async indexCode(options = {}) {
|
|
1831
|
-
const mods = this._d.registry.allByType("code");
|
|
1932
|
+
const mods = this._d.registry.allByType("code").filter(isIndexable);
|
|
1832
1933
|
if (!mods.length) throw new Error("BrainBank: Indexer 'code' is not loaded. Add .use(code()) to your BrainBank instance.");
|
|
1833
1934
|
const acc = { indexed: 0, skipped: 0, chunks: 0 };
|
|
1834
1935
|
for (const mod of mods) {
|
|
@@ -1840,7 +1941,7 @@ var IndexAPI = class {
|
|
|
1840
1941
|
return acc;
|
|
1841
1942
|
}
|
|
1842
1943
|
async indexGit(options = {}) {
|
|
1843
|
-
const mods = this._d.registry.allByType("git");
|
|
1944
|
+
const mods = this._d.registry.allByType("git").filter(isIndexable);
|
|
1844
1945
|
if (!mods.length) throw new Error("BrainBank: Indexer 'git' is not loaded. Add .use(git()) to your BrainBank instance.");
|
|
1845
1946
|
const acc = { indexed: 0, skipped: 0 };
|
|
1846
1947
|
for (const mod of mods) {
|
|
@@ -1868,7 +1969,7 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
1868
1969
|
let timer = null;
|
|
1869
1970
|
const customPatterns = [];
|
|
1870
1971
|
for (const indexer of indexers.values()) {
|
|
1871
|
-
if (indexer
|
|
1972
|
+
if (isWatchable(indexer)) {
|
|
1872
1973
|
customPatterns.push({ indexer, patterns: indexer.watchPatterns() });
|
|
1873
1974
|
}
|
|
1874
1975
|
}
|
|
@@ -1895,35 +1996,44 @@ function createWatcher(reindexFn, indexers, repoPath, options = {}) {
|
|
|
1895
1996
|
return filePath === pattern;
|
|
1896
1997
|
}
|
|
1897
1998
|
__name(matchGlob, "matchGlob");
|
|
1999
|
+
let flushing = false;
|
|
1898
2000
|
async function flush() {
|
|
1899
|
-
if (pending.size === 0) return;
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
const
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2001
|
+
if (flushing || pending.size === 0) return;
|
|
2002
|
+
flushing = true;
|
|
2003
|
+
try {
|
|
2004
|
+
const files = [...pending];
|
|
2005
|
+
pending.clear();
|
|
2006
|
+
let needsReindex = false;
|
|
2007
|
+
for (const filePath of files) {
|
|
2008
|
+
const absPath = path3.resolve(repoPath, filePath);
|
|
2009
|
+
const customIndexer = matchCustomIndexer(absPath);
|
|
2010
|
+
if (customIndexer && isWatchable(customIndexer)) {
|
|
2011
|
+
try {
|
|
2012
|
+
const handled = await customIndexer.onFileChange(absPath, detectEvent(absPath));
|
|
2013
|
+
if (handled) {
|
|
2014
|
+
onIndex?.(filePath, customIndexer.name);
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
} catch (err) {
|
|
2018
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
1912
2019
|
}
|
|
2020
|
+
}
|
|
2021
|
+
if (isSupported(filePath)) {
|
|
2022
|
+
needsReindex = true;
|
|
2023
|
+
onIndex?.(filePath, "code");
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (needsReindex) {
|
|
2027
|
+
try {
|
|
2028
|
+
await reindexFn();
|
|
1913
2029
|
} catch (err) {
|
|
1914
2030
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
1915
2031
|
}
|
|
1916
2032
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
}
|
|
1922
|
-
if (needsReindex) {
|
|
1923
|
-
try {
|
|
1924
|
-
await reindexFn();
|
|
1925
|
-
} catch (err) {
|
|
1926
|
-
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
2033
|
+
} finally {
|
|
2034
|
+
flushing = false;
|
|
2035
|
+
if (pending.size > 0) {
|
|
2036
|
+
timer = setTimeout(() => flush(), debounceMs);
|
|
1927
2037
|
}
|
|
1928
2038
|
}
|
|
1929
2039
|
}
|
|
@@ -2115,6 +2225,12 @@ var BrainBank = class extends EventEmitter {
|
|
|
2115
2225
|
this._requireInit("listCollectionNames");
|
|
2116
2226
|
return this._db.prepare("SELECT DISTINCT collection FROM kv_data ORDER BY collection").all().map((r) => r.collection);
|
|
2117
2227
|
}
|
|
2228
|
+
/** Delete a collection's data and evict from cache. */
|
|
2229
|
+
deleteCollection(name) {
|
|
2230
|
+
this._requireInit("deleteCollection");
|
|
2231
|
+
this._db.prepare("DELETE FROM kv_data WHERE collection = ?").run(name);
|
|
2232
|
+
this._collections.delete(name);
|
|
2233
|
+
}
|
|
2118
2234
|
// ── Indexing (delegated to IndexAPI) ─────────────
|
|
2119
2235
|
async index(options = {}) {
|
|
2120
2236
|
await this.initialize();
|
|
@@ -2134,26 +2250,21 @@ var BrainBank = class extends EventEmitter {
|
|
|
2134
2250
|
/** Register a document collection. */
|
|
2135
2251
|
async addCollection(collection) {
|
|
2136
2252
|
await this.initialize();
|
|
2137
|
-
this.
|
|
2138
|
-
this.indexer("docs").addCollection(collection);
|
|
2253
|
+
this._docsPlugin("addCollection").addCollection(collection);
|
|
2139
2254
|
}
|
|
2140
2255
|
/** Remove a collection and all its indexed data. */
|
|
2141
2256
|
async removeCollection(name) {
|
|
2142
2257
|
await this.initialize();
|
|
2143
|
-
this.
|
|
2144
|
-
this.indexer("docs").removeCollection(name);
|
|
2258
|
+
this._docsPlugin("removeCollection").removeCollection(name);
|
|
2145
2259
|
}
|
|
2146
2260
|
/** List all registered collections. */
|
|
2147
2261
|
listCollections() {
|
|
2148
|
-
this.
|
|
2149
|
-
this._requireDocs("listCollections");
|
|
2150
|
-
return this.indexer("docs").listCollections();
|
|
2262
|
+
return this._docsPlugin("listCollections").listCollections();
|
|
2151
2263
|
}
|
|
2152
2264
|
/** Index all (or specific) document collections. */
|
|
2153
2265
|
async indexDocs(options = {}) {
|
|
2154
2266
|
await this.initialize();
|
|
2155
|
-
this.
|
|
2156
|
-
const results = await this.indexer("docs").indexCollections(options);
|
|
2267
|
+
const results = await this._docsPlugin("indexDocs").indexCollections(options);
|
|
2157
2268
|
this.emit("docsIndexed", results);
|
|
2158
2269
|
return results;
|
|
2159
2270
|
}
|
|
@@ -2161,23 +2272,23 @@ var BrainBank = class extends EventEmitter {
|
|
|
2161
2272
|
async searchDocs(query, options) {
|
|
2162
2273
|
await this.initialize();
|
|
2163
2274
|
if (!this.has("docs")) return [];
|
|
2164
|
-
return this.
|
|
2275
|
+
return this._docsPlugin("searchDocs").search(query, options);
|
|
2165
2276
|
}
|
|
2166
2277
|
// ── Context metadata ─────────────────────────────
|
|
2167
2278
|
/** Add context description for a collection path. */
|
|
2168
2279
|
addContext(collection, path4, context) {
|
|
2169
|
-
this.
|
|
2170
|
-
|
|
2280
|
+
const docs = this._docsPlugin("addContext");
|
|
2281
|
+
if (docs.addContext) docs.addContext(collection, path4, context);
|
|
2171
2282
|
}
|
|
2172
2283
|
/** Remove context for a collection path. */
|
|
2173
2284
|
removeContext(collection, path4) {
|
|
2174
|
-
this.
|
|
2175
|
-
|
|
2285
|
+
const docs = this._docsPlugin("removeContext");
|
|
2286
|
+
if (docs.removeContext) docs.removeContext(collection, path4);
|
|
2176
2287
|
}
|
|
2177
2288
|
/** List all context entries. */
|
|
2178
2289
|
listContexts() {
|
|
2179
|
-
this.
|
|
2180
|
-
return
|
|
2290
|
+
const docs = this._docsPlugin("listContexts");
|
|
2291
|
+
return docs.listContexts?.() ?? [];
|
|
2181
2292
|
}
|
|
2182
2293
|
// ── Search (delegated to SearchAPI) ─────────────
|
|
2183
2294
|
/**
|
|
@@ -2318,9 +2429,13 @@ var BrainBank = class extends EventEmitter {
|
|
|
2318
2429
|
if (!this._initialized)
|
|
2319
2430
|
throw new Error(`BrainBank: Not initialized. Call await brain.initialize() before ${method}().`);
|
|
2320
2431
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2432
|
+
/** Get the docs indexer as CollectionPlugin with init + type check. */
|
|
2433
|
+
_docsPlugin(method) {
|
|
2434
|
+
this._requireInit(method);
|
|
2435
|
+
const docs = this._registry.get("docs");
|
|
2436
|
+
if (!docs || !isCollectionPlugin(docs))
|
|
2323
2437
|
throw new Error(`BrainBank: Docs indexer not loaded. Add .use(docs()) before calling ${method}().`);
|
|
2438
|
+
return docs;
|
|
2324
2439
|
}
|
|
2325
2440
|
};
|
|
2326
2441
|
|
|
@@ -2336,4 +2451,4 @@ export {
|
|
|
2336
2451
|
ContextBuilder,
|
|
2337
2452
|
BrainBank
|
|
2338
2453
|
};
|
|
2339
|
-
//# sourceMappingURL=chunk-
|
|
2454
|
+
//# sourceMappingURL=chunk-MY36UPPQ.js.map
|