brainbank 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/assets/architecture.png +0 -0
- package/bin/brainbank +18 -0
- package/bin/brainbank-mcp +19 -0
- package/dist/chunk-3YBCD6DI.js +117 -0
- package/dist/chunk-3YBCD6DI.js.map +1 -0
- package/dist/chunk-63GBCDS5.js +3249 -0
- package/dist/chunk-63GBCDS5.js.map +1 -0
- package/dist/chunk-DMFMTOHF.js +123 -0
- package/dist/chunk-DMFMTOHF.js.map +1 -0
- package/dist/chunk-FQYKWB2Q.js +136 -0
- package/dist/chunk-FQYKWB2Q.js.map +1 -0
- package/dist/chunk-IMJJ2VEM.js +74 -0
- package/dist/chunk-IMJJ2VEM.js.map +1 -0
- package/dist/chunk-M744PCJQ.js +43 -0
- package/dist/chunk-M744PCJQ.js.map +1 -0
- package/dist/chunk-O3J6ZIXK.js +82 -0
- package/dist/chunk-O3J6ZIXK.js.map +1 -0
- package/dist/chunk-OPH7GZ7U.js +124 -0
- package/dist/chunk-OPH7GZ7U.js.map +1 -0
- package/dist/chunk-PXEWQMN7.js +89 -0
- package/dist/chunk-PXEWQMN7.js.map +1 -0
- package/dist/chunk-RDQYDLYZ.js +69 -0
- package/dist/chunk-RDQYDLYZ.js.map +1 -0
- package/dist/chunk-VIIHPCC4.js +254 -0
- package/dist/chunk-VIIHPCC4.js.map +1 -0
- package/dist/chunk-WCQVDF3K.js +14 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3076 -0
- package/dist/cli.js.map +1 -0
- package/dist/haiku-expander-YRSIPGKP.js +8 -0
- package/dist/haiku-expander-YRSIPGKP.js.map +1 -0
- package/dist/haiku-pruner-SHAXUPY6.js +8 -0
- package/dist/haiku-pruner-SHAXUPY6.js.map +1 -0
- package/dist/http-server-QUXHLWUM.js +9 -0
- package/dist/http-server-QUXHLWUM.js.map +1 -0
- package/dist/index.d.ts +2161 -0
- package/dist/index.js +357 -0
- package/dist/index.js.map +1 -0
- package/dist/local-embedding-NZQTILGV.js +8 -0
- package/dist/local-embedding-NZQTILGV.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +334 -0
- package/dist/mcp.js.map +1 -0
- package/dist/openai-embedding-ZP5TSUJG.js +8 -0
- package/dist/openai-embedding-ZP5TSUJG.js.map +1 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
- package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
- package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
- package/dist/plugin-IKQ6IRSJ.js +32 -0
- package/dist/plugin-IKQ6IRSJ.js.map +1 -0
- package/dist/resolve-ASGLBNUC.js +10 -0
- package/dist/resolve-ASGLBNUC.js.map +1 -0
- package/dist/stats-tui-ZY2NQSEA.js +1904 -0
- package/dist/stats-tui-ZY2NQSEA.js.map +1 -0
- package/package.json +96 -0
- package/src/brainbank.ts +617 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +179 -0
- package/src/cli/commands/daemon.ts +100 -0
- package/src/cli/commands/docs.ts +71 -0
- package/src/cli/commands/files.ts +69 -0
- package/src/cli/commands/help.ts +77 -0
- package/src/cli/commands/index.ts +482 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp-export.ts +273 -0
- package/src/cli/commands/mcp.ts +6 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +336 -0
- package/src/cli/commands/search.ts +203 -0
- package/src/cli/commands/stats.ts +68 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +47 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +87 -0
- package/src/cli/factory/config-loader.ts +77 -0
- package/src/cli/factory/index.ts +69 -0
- package/src/cli/factory/plugin-loader.ts +325 -0
- package/src/cli/index.ts +71 -0
- package/src/cli/server-client.ts +178 -0
- package/src/cli/tui/index-tui.tsx +667 -0
- package/src/cli/tui/stats-data.ts +523 -0
- package/src/cli/tui/stats-search.ts +262 -0
- package/src/cli/tui/stats-tui.tsx +1465 -0
- package/src/cli/tui/tree-scanner.ts +650 -0
- package/src/cli/utils.ts +137 -0
- package/src/config.ts +49 -0
- package/src/constants.ts +21 -0
- package/src/db/adapter.ts +112 -0
- package/src/db/metadata.ts +130 -0
- package/src/db/migrations.ts +66 -0
- package/src/db/sqlite-adapter.ts +218 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +81 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +218 -0
- package/src/index.ts +154 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +126 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +71 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/mcp/mcp-server.ts +195 -0
- package/src/mcp/workspace-factory.ts +68 -0
- package/src/mcp/workspace-pool.ts +224 -0
- package/src/plugin.ts +381 -0
- package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
- package/src/providers/embeddings/embedding-worker.ts +141 -0
- package/src/providers/embeddings/local-embedding.ts +115 -0
- package/src/providers/embeddings/openai-embedding.ts +167 -0
- package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
- package/src/providers/embeddings/perplexity-embedding.ts +165 -0
- package/src/providers/embeddings/resolve.ts +34 -0
- package/src/providers/pruners/haiku-expander.ts +166 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/vector/hnsw-index.ts +174 -0
- package/src/providers/vector/hnsw-loader.ts +129 -0
- package/src/search/bm25-boost.ts +69 -0
- package/src/search/context-builder.ts +251 -0
- package/src/search/keyword/composite-bm25-search.ts +47 -0
- package/src/search/types.ts +37 -0
- package/src/search/vector/composite-vector-search.ts +61 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +384 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +336 -0
- package/src/services/kv-service.ts +64 -0
- package/src/services/plugin-registry.ts +77 -0
- package/src/services/watch.ts +340 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +493 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2161 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BrainBank — Database Adapter Interface
|
|
5
|
+
*
|
|
6
|
+
* Abstract contract for database operations. All consumers depend on
|
|
7
|
+
* this interface — never on a concrete driver. The `SQLiteAdapter`
|
|
8
|
+
* is the built-in implementation; future adapters (LibSQL, Turso,
|
|
9
|
+
* PostgreSQL) implement the same contract.
|
|
10
|
+
*
|
|
11
|
+
* Phase 1: sync-first API matching the existing synchronous SQLite usage.
|
|
12
|
+
* Async variants will be added when needed by async-native adapters.
|
|
13
|
+
*/
|
|
14
|
+
/** Result from mutating queries (INSERT / UPDATE / DELETE). */
|
|
15
|
+
interface ExecuteResult {
|
|
16
|
+
/** Row ID of the last inserted row. */
|
|
17
|
+
lastInsertRowid: number | bigint;
|
|
18
|
+
/** Number of rows changed by the statement. */
|
|
19
|
+
changes: number;
|
|
20
|
+
}
|
|
21
|
+
/** A prepared statement with typed query methods. */
|
|
22
|
+
interface PreparedStatement<T = unknown> {
|
|
23
|
+
/** Execute a query and return the first matching row, or `undefined`. */
|
|
24
|
+
get(...params: unknown[]): T | undefined;
|
|
25
|
+
/** Execute a query and return all matching rows. */
|
|
26
|
+
all(...params: unknown[]): T[];
|
|
27
|
+
/** Execute a mutating statement and return the result. */
|
|
28
|
+
run(...params: unknown[]): ExecuteResult;
|
|
29
|
+
/** Iterate over matching rows without loading them all into memory. */
|
|
30
|
+
iterate(...params: unknown[]): IterableIterator<T>;
|
|
31
|
+
}
|
|
32
|
+
/** Adapter capability flags — describes what the underlying engine supports. */
|
|
33
|
+
interface AdapterCapabilities {
|
|
34
|
+
/** Full-text search engine. */
|
|
35
|
+
fts: 'fts5' | 'tsvector' | 'none';
|
|
36
|
+
/** Upsert syntax dialect. */
|
|
37
|
+
upsert: 'or-replace' | 'on-conflict';
|
|
38
|
+
/** Native JSON column support. */
|
|
39
|
+
json: boolean;
|
|
40
|
+
/** Native vector column support (e.g. pgvector). */
|
|
41
|
+
vectors: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Database adapter interface.
|
|
45
|
+
*
|
|
46
|
+
* All BrainBank components depend on this contract instead of a
|
|
47
|
+
* concrete database driver. Keeps the door open for LibSQL, Turso,
|
|
48
|
+
* PostgreSQL, etc. without touching consumer code.
|
|
49
|
+
*/
|
|
50
|
+
interface DatabaseAdapter {
|
|
51
|
+
/** Prepare a reusable statement. */
|
|
52
|
+
prepare<T = unknown>(sql: string): PreparedStatement<T>;
|
|
53
|
+
/** Execute raw DDL / multi-statement SQL (no results). */
|
|
54
|
+
exec(sql: string): void;
|
|
55
|
+
/** Run `fn` inside a transaction. Auto-commits on success, auto-rollbacks on error. */
|
|
56
|
+
transaction<T>(fn: () => T): T;
|
|
57
|
+
/** Run a prepared statement on multiple rows inside a single transaction. */
|
|
58
|
+
batch<T extends unknown[]>(sql: string, rows: T[]): void;
|
|
59
|
+
/** Close the database and release resources. */
|
|
60
|
+
close(): void;
|
|
61
|
+
/** Engine capabilities (FTS, upsert dialect, etc.). */
|
|
62
|
+
readonly capabilities: AdapterCapabilities;
|
|
63
|
+
/**
|
|
64
|
+
* Escape hatch: access the underlying raw driver.
|
|
65
|
+
* Returns `undefined` for adapters that don't support raw access.
|
|
66
|
+
*
|
|
67
|
+
* @deprecated Use `DatabaseAdapter` methods instead. This exists
|
|
68
|
+
* only for gradual migration of plugins that depend on driver internals.
|
|
69
|
+
*/
|
|
70
|
+
raw<T = unknown>(): T | undefined;
|
|
71
|
+
}
|
|
72
|
+
interface KvDataRow {
|
|
73
|
+
id: number;
|
|
74
|
+
collection: string;
|
|
75
|
+
content: string;
|
|
76
|
+
meta_json: string;
|
|
77
|
+
tags_json: string;
|
|
78
|
+
expires_at: number | null;
|
|
79
|
+
created_at: number;
|
|
80
|
+
}
|
|
81
|
+
interface KvVectorRow {
|
|
82
|
+
data_id: number;
|
|
83
|
+
embedding: Uint8Array;
|
|
84
|
+
}
|
|
85
|
+
interface EmbeddingMetaRow {
|
|
86
|
+
value: string;
|
|
87
|
+
}
|
|
88
|
+
interface VectorRow {
|
|
89
|
+
id: number;
|
|
90
|
+
embedding: Uint8Array;
|
|
91
|
+
}
|
|
92
|
+
interface CountRow {
|
|
93
|
+
c: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* BrainBank — Plugin Migration System
|
|
98
|
+
*
|
|
99
|
+
* Per-plugin versioned schema migrations.
|
|
100
|
+
* Each plugin declares a schemaVersion + ordered migrations array.
|
|
101
|
+
* Core stores applied versions in `plugin_versions` table.
|
|
102
|
+
*
|
|
103
|
+
* Plugins call `runPluginMigrations()` at the top of their `initialize()`.
|
|
104
|
+
* Migrations use `IF NOT EXISTS` so first run on an existing DB is a no-op.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/** A single migration step. */
|
|
108
|
+
interface Migration {
|
|
109
|
+
/** Version this migration brings the schema to. */
|
|
110
|
+
version: number;
|
|
111
|
+
/** Apply the migration. Must be idempotent (use IF NOT EXISTS). */
|
|
112
|
+
up(adapter: DatabaseAdapter): void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Run pending migrations for a plugin.
|
|
116
|
+
* Skips migrations whose version <= stored version.
|
|
117
|
+
* Each migration runs in its own transaction.
|
|
118
|
+
*/
|
|
119
|
+
declare function runPluginMigrations(adapter: DatabaseAdapter, pluginName: string, schemaVersion: number, migrations: Migration[]): void;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* BrainBank — Incremental Tracker
|
|
123
|
+
*
|
|
124
|
+
* Standardized helper for plugins to detect add/update/delete during indexing.
|
|
125
|
+
* Uses a shared `plugin_tracking` table with per-plugin namespacing.
|
|
126
|
+
*
|
|
127
|
+
* Usage in a plugin:
|
|
128
|
+
* const tracker = ctx.createTracker(); // uses plugin name
|
|
129
|
+
* for (const file of files) {
|
|
130
|
+
* const hash = sha256(content);
|
|
131
|
+
* if (tracker.isUnchanged(file, hash)) { skipped++; continue; }
|
|
132
|
+
* indexFile(file, content);
|
|
133
|
+
* tracker.markIndexed(file, hash);
|
|
134
|
+
* }
|
|
135
|
+
* const orphans = tracker.findOrphans(new Set(files));
|
|
136
|
+
* for (const key of orphans) { removeData(key); tracker.remove(key); }
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
/** Incremental index tracker — detects add/update/delete for plugin files. */
|
|
140
|
+
interface IncrementalTracker {
|
|
141
|
+
/** Check if a key's content is unchanged. Returns true if the hash matches (skip indexing). */
|
|
142
|
+
isUnchanged(key: string, contentHash: string): boolean;
|
|
143
|
+
/** Mark a key as successfully indexed with the given hash. Call after indexing completes. */
|
|
144
|
+
markIndexed(key: string, contentHash: string): void;
|
|
145
|
+
/** Find tracked keys that are NOT in the current set. Returns keys to delete. */
|
|
146
|
+
findOrphans(currentKeys: Set<string>): string[];
|
|
147
|
+
/** Remove tracking for a key. Call after cleaning up the key's data. */
|
|
148
|
+
remove(key: string): void;
|
|
149
|
+
/** Remove all tracking entries for this plugin. */
|
|
150
|
+
clear(): void;
|
|
151
|
+
}
|
|
152
|
+
/** Create an IncrementalTracker scoped to a plugin name. */
|
|
153
|
+
declare function createTracker(db: DatabaseAdapter, pluginName: string): IncrementalTracker;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* BrainBank — Collection
|
|
157
|
+
*
|
|
158
|
+
* Universal key-value store with vector + BM25 hybrid search.
|
|
159
|
+
* The foundation primitive — store anything, search semantically.
|
|
160
|
+
*
|
|
161
|
+
* const errors = brain.collection('debug_errors');
|
|
162
|
+
* await errors.add('Fixed null check in api handler', { file: 'api.ts' });
|
|
163
|
+
* const hits = await errors.search('null pointer');
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
interface CollectionItem {
|
|
167
|
+
id: number;
|
|
168
|
+
collection: string;
|
|
169
|
+
content: string;
|
|
170
|
+
metadata: Record<string, unknown>;
|
|
171
|
+
tags: string[];
|
|
172
|
+
createdAt: number;
|
|
173
|
+
expiresAt?: number;
|
|
174
|
+
score?: number;
|
|
175
|
+
}
|
|
176
|
+
interface CollectionSearchOptions {
|
|
177
|
+
/** Max results. Default: 5 */
|
|
178
|
+
k?: number;
|
|
179
|
+
/** Search mode. Default: 'hybrid' */
|
|
180
|
+
mode?: 'hybrid' | 'vector' | 'keyword';
|
|
181
|
+
/** Minimum score threshold. Default: 0.15 */
|
|
182
|
+
minScore?: number;
|
|
183
|
+
/** Filter by tags (item must have ALL specified tags). */
|
|
184
|
+
tags?: string[];
|
|
185
|
+
}
|
|
186
|
+
interface CollectionAddOptions {
|
|
187
|
+
/** Metadata key-value pairs. */
|
|
188
|
+
metadata?: Record<string, unknown>;
|
|
189
|
+
/** Tags for filtering. */
|
|
190
|
+
tags?: string[];
|
|
191
|
+
/** Time-to-live duration string (e.g. '7d', '24h', '30m'). */
|
|
192
|
+
ttl?: string;
|
|
193
|
+
}
|
|
194
|
+
declare class Collection {
|
|
195
|
+
private _name;
|
|
196
|
+
private _db;
|
|
197
|
+
private _embedding;
|
|
198
|
+
private _hnsw;
|
|
199
|
+
private _vecs;
|
|
200
|
+
constructor(_name: string, _db: DatabaseAdapter, _embedding: EmbeddingProvider, _hnsw: HNSWIndex, _vecs: Map<number, Float32Array>);
|
|
201
|
+
/** Collection name. */
|
|
202
|
+
get name(): string;
|
|
203
|
+
/** Add an item. Returns its ID. */
|
|
204
|
+
add(content: string, options?: CollectionAddOptions | Record<string, unknown>): Promise<number>;
|
|
205
|
+
/** Update an item's content (re-embeds). Returns the new ID. */
|
|
206
|
+
update(id: number, content: string, options?: CollectionAddOptions): Promise<number>;
|
|
207
|
+
/** Add multiple items. Returns their IDs. */
|
|
208
|
+
addMany(items: {
|
|
209
|
+
content: string;
|
|
210
|
+
metadata?: Record<string, unknown>;
|
|
211
|
+
tags?: string[];
|
|
212
|
+
ttl?: string;
|
|
213
|
+
}[]): Promise<number[]>;
|
|
214
|
+
/** Search this collection. */
|
|
215
|
+
search(query: string, options?: CollectionSearchOptions): Promise<CollectionItem[]>;
|
|
216
|
+
/** Search and return results as SearchResult[] for use in hybrid search pipelines. */
|
|
217
|
+
searchAsResults(query: string, k: number): Promise<SearchResult[]>;
|
|
218
|
+
/** List items (newest first). */
|
|
219
|
+
list(options?: {
|
|
220
|
+
limit?: number;
|
|
221
|
+
offset?: number;
|
|
222
|
+
tags?: string[];
|
|
223
|
+
}): CollectionItem[];
|
|
224
|
+
/** Count items in this collection. */
|
|
225
|
+
count(): number;
|
|
226
|
+
/** Keep only the N most recent items, remove the rest. */
|
|
227
|
+
trim(options: {
|
|
228
|
+
keep: number;
|
|
229
|
+
}): Promise<{
|
|
230
|
+
removed: number;
|
|
231
|
+
}>;
|
|
232
|
+
/** Remove items older than a duration string (e.g. '30d', '12h'). */
|
|
233
|
+
prune(options: {
|
|
234
|
+
olderThan: string;
|
|
235
|
+
}): Promise<{
|
|
236
|
+
removed: number;
|
|
237
|
+
}>;
|
|
238
|
+
/** Remove a specific item by ID. */
|
|
239
|
+
remove(id: number): void;
|
|
240
|
+
/** Clear all items in this collection. */
|
|
241
|
+
clear(): void;
|
|
242
|
+
private _removeById;
|
|
243
|
+
private _searchVector;
|
|
244
|
+
/** Compute adaptive over-fetch multiplier based on collection density in shared HNSW. */
|
|
245
|
+
private _adaptiveSearchK;
|
|
246
|
+
private _searchBM25;
|
|
247
|
+
private _rowToItem;
|
|
248
|
+
/** Filter results by tags (item must have ALL specified tags). */
|
|
249
|
+
private _filterByTags;
|
|
250
|
+
/** Remove expired items (TTL). Called automatically on search/list. */
|
|
251
|
+
private _pruneExpired;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* BrainBank — Type Definitions
|
|
256
|
+
*
|
|
257
|
+
* All interfaces and types for the semantic knowledge bank.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/** Public contract for a KV collection. Plugins depend on this interface, not the concrete class. */
|
|
261
|
+
interface ICollection {
|
|
262
|
+
/** Collection name. */
|
|
263
|
+
readonly name: string;
|
|
264
|
+
/** Add an item. Returns its ID. */
|
|
265
|
+
add(content: string, options?: CollectionAddOptions | Record<string, unknown>): Promise<number>;
|
|
266
|
+
/** Update an item's content (re-embeds). Returns the new ID. */
|
|
267
|
+
update(id: number, content: string, options?: CollectionAddOptions): Promise<number>;
|
|
268
|
+
/** Add multiple items. Returns their IDs. */
|
|
269
|
+
addMany(items: {
|
|
270
|
+
content: string;
|
|
271
|
+
metadata?: Record<string, unknown>;
|
|
272
|
+
tags?: string[];
|
|
273
|
+
ttl?: string;
|
|
274
|
+
}[]): Promise<number[]>;
|
|
275
|
+
/** Search this collection. */
|
|
276
|
+
search(query: string, options?: CollectionSearchOptions): Promise<CollectionItem[]>;
|
|
277
|
+
/** List items (newest first). */
|
|
278
|
+
list(options?: {
|
|
279
|
+
limit?: number;
|
|
280
|
+
offset?: number;
|
|
281
|
+
tags?: string[];
|
|
282
|
+
}): CollectionItem[];
|
|
283
|
+
/** Count items in this collection. */
|
|
284
|
+
count(): number;
|
|
285
|
+
/** Keep only the N most recent items. */
|
|
286
|
+
trim(options: {
|
|
287
|
+
keep: number;
|
|
288
|
+
}): Promise<{
|
|
289
|
+
removed: number;
|
|
290
|
+
}>;
|
|
291
|
+
/** Remove items older than a duration string. */
|
|
292
|
+
prune(options: {
|
|
293
|
+
olderThan: string;
|
|
294
|
+
}): Promise<{
|
|
295
|
+
removed: number;
|
|
296
|
+
}>;
|
|
297
|
+
/** Remove a specific item by ID. */
|
|
298
|
+
remove(id: number): void;
|
|
299
|
+
/** Clear all items in this collection. */
|
|
300
|
+
clear(): void;
|
|
301
|
+
}
|
|
302
|
+
interface BrainBankConfig {
|
|
303
|
+
/** Root path of the repository to index. Default: '.' */
|
|
304
|
+
repoPath?: string;
|
|
305
|
+
/** SQLite database path. Default: '.brainbank/data/brainbank.db' */
|
|
306
|
+
dbPath?: string;
|
|
307
|
+
/** Max git commits to index. Default: 500 */
|
|
308
|
+
gitDepth?: number;
|
|
309
|
+
/** Max file size in bytes to index. Default: 512_000 (500KB) */
|
|
310
|
+
maxFileSize?: number;
|
|
311
|
+
/** Max diff bytes per commit. Default: 8192 */
|
|
312
|
+
maxDiffBytes?: number;
|
|
313
|
+
/** HNSW M parameter (connections per node). Default: 16 */
|
|
314
|
+
hnswM?: number;
|
|
315
|
+
/** HNSW efConstruction (build-time candidates). Default: 200 */
|
|
316
|
+
hnswEfConstruction?: number;
|
|
317
|
+
/** HNSW efSearch (query-time candidates). Default: 50 */
|
|
318
|
+
hnswEfSearch?: number;
|
|
319
|
+
/** Embedding dimensions. Default: 384 */
|
|
320
|
+
embeddingDims?: number;
|
|
321
|
+
/** Max HNSW elements. Default: 2_000_000 */
|
|
322
|
+
maxElements?: number;
|
|
323
|
+
/** Custom embedding provider (default: local WASM model) */
|
|
324
|
+
embeddingProvider?: EmbeddingProvider;
|
|
325
|
+
/** Optional LLM noise filter — drops irrelevant results before formatting */
|
|
326
|
+
pruner?: Pruner;
|
|
327
|
+
/** Optional LLM context expander — discovers additional relevant chunks after pruning */
|
|
328
|
+
expander?: Expander;
|
|
329
|
+
/** Port for optional webhook server (enables push-based watch plugins). */
|
|
330
|
+
webhookPort?: number;
|
|
331
|
+
/** Context field defaults from config.json "context" section. */
|
|
332
|
+
contextFields?: Record<string, unknown>;
|
|
333
|
+
}
|
|
334
|
+
interface ResolvedConfig {
|
|
335
|
+
repoPath: string;
|
|
336
|
+
dbPath: string;
|
|
337
|
+
gitDepth: number;
|
|
338
|
+
maxFileSize: number;
|
|
339
|
+
maxDiffBytes: number;
|
|
340
|
+
hnswM: number;
|
|
341
|
+
hnswEfConstruction: number;
|
|
342
|
+
hnswEfSearch: number;
|
|
343
|
+
embeddingDims: number;
|
|
344
|
+
maxElements: number;
|
|
345
|
+
embeddingProvider?: EmbeddingProvider;
|
|
346
|
+
pruner?: Pruner;
|
|
347
|
+
expander?: Expander;
|
|
348
|
+
webhookPort?: number;
|
|
349
|
+
/** Context field defaults from config.json "context" section. */
|
|
350
|
+
contextFields?: Record<string, unknown>;
|
|
351
|
+
}
|
|
352
|
+
interface EmbeddingProvider {
|
|
353
|
+
/** Vector dimensions produced by this provider. */
|
|
354
|
+
readonly dims: number;
|
|
355
|
+
/** Embed a single text string. */
|
|
356
|
+
embed(text: string): Promise<Float32Array>;
|
|
357
|
+
/** Embed multiple texts (batch). */
|
|
358
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
359
|
+
/** Release resources. */
|
|
360
|
+
close(): Promise<void>;
|
|
361
|
+
}
|
|
362
|
+
/** Item passed to the pruner for noise classification. */
|
|
363
|
+
interface PrunerItem {
|
|
364
|
+
/** Positional index (used to map back to SearchResult[]) */
|
|
365
|
+
id: number;
|
|
366
|
+
/** File path — primary signal for relevance */
|
|
367
|
+
filePath: string;
|
|
368
|
+
/** Trimmed content preview */
|
|
369
|
+
preview: string;
|
|
370
|
+
/** Chunk metadata (type, name, language, lines, etc.) */
|
|
371
|
+
metadata: Record<string, unknown>;
|
|
372
|
+
}
|
|
373
|
+
interface Pruner {
|
|
374
|
+
/**
|
|
375
|
+
* Filter noise from search results and order by semantic relevance.
|
|
376
|
+
* @param query - The search query
|
|
377
|
+
* @param items - Items to evaluate (filePath + metadata + trimmed preview)
|
|
378
|
+
* @returns Array of item IDs to KEEP, ordered by relevance to the query
|
|
379
|
+
* (most relevant first). Everything else is dropped.
|
|
380
|
+
*/
|
|
381
|
+
prune(query: string, items: PrunerItem[]): Promise<number[]>;
|
|
382
|
+
/** Release resources. */
|
|
383
|
+
close?(): Promise<void>;
|
|
384
|
+
}
|
|
385
|
+
/** Lightweight chunk descriptor for the expander manifest. */
|
|
386
|
+
interface ExpanderManifestItem {
|
|
387
|
+
/** Chunk ID (code_chunks.id) */
|
|
388
|
+
id: number;
|
|
389
|
+
/** File path */
|
|
390
|
+
filePath: string;
|
|
391
|
+
/** Chunk name (e.g. function/class name) */
|
|
392
|
+
name: string;
|
|
393
|
+
/** Chunk type (function, class, method, etc.) */
|
|
394
|
+
chunkType: string;
|
|
395
|
+
/** Line range (e.g. "L45-L89") */
|
|
396
|
+
lines: string;
|
|
397
|
+
/** True if this chunk is from an import-graph neighbor of the search results. */
|
|
398
|
+
priority?: boolean;
|
|
399
|
+
}
|
|
400
|
+
/** Result from the expander: chunk IDs to add + optional free-text note. */
|
|
401
|
+
interface ExpanderResult {
|
|
402
|
+
/** Additional chunk IDs to splice into results. */
|
|
403
|
+
ids: number[];
|
|
404
|
+
/** Brief observation for the agent (e.g. "file X not found", "module Y is deprecated"). */
|
|
405
|
+
note?: string;
|
|
406
|
+
}
|
|
407
|
+
interface Expander {
|
|
408
|
+
/**
|
|
409
|
+
* Given a task and a manifest of available chunks,
|
|
410
|
+
* return additional chunk IDs + an optional contextual note.
|
|
411
|
+
* Runs after pruning. Fail-open: errors return empty result.
|
|
412
|
+
*
|
|
413
|
+
* @param query - The search query / task description
|
|
414
|
+
* @param currentIds - IDs of chunks already in results (from search + prune)
|
|
415
|
+
* @param manifest - All available chunks (lightweight descriptors)
|
|
416
|
+
* @returns Chunk IDs to add + optional note
|
|
417
|
+
*/
|
|
418
|
+
expand(query: string, currentIds: number[], manifest: ExpanderManifestItem[]): Promise<ExpanderResult>;
|
|
419
|
+
/** Release resources. */
|
|
420
|
+
close?(): Promise<void>;
|
|
421
|
+
}
|
|
422
|
+
interface SearchHit {
|
|
423
|
+
id: number;
|
|
424
|
+
score: number;
|
|
425
|
+
}
|
|
426
|
+
interface VectorIndex {
|
|
427
|
+
/** Initialize the index. Must be called before add/search. */
|
|
428
|
+
init(): Promise<this>;
|
|
429
|
+
/** Add a vector with an integer ID. Idempotent: duplicate IDs are skipped. */
|
|
430
|
+
add(vector: Float32Array, id: number): void;
|
|
431
|
+
/** Mark a vector as deleted so it no longer appears in searches. */
|
|
432
|
+
remove(id: number): void;
|
|
433
|
+
/** Search for k nearest neighbors. */
|
|
434
|
+
search(query: Float32Array, k: number): SearchHit[];
|
|
435
|
+
/** Clear all vectors and reset to empty state. */
|
|
436
|
+
reinit(): void;
|
|
437
|
+
/** Number of vectors in the index. */
|
|
438
|
+
readonly size: number;
|
|
439
|
+
}
|
|
440
|
+
interface CodeChunk {
|
|
441
|
+
/** Auto-incremented DB id (set after insert) */
|
|
442
|
+
id?: number;
|
|
443
|
+
/** Relative file path from repo root */
|
|
444
|
+
filePath: string;
|
|
445
|
+
/** Chunk type: 'file' | 'function' | 'class' | 'block' */
|
|
446
|
+
chunkType: string;
|
|
447
|
+
/** Function/class name (if detected) */
|
|
448
|
+
name?: string;
|
|
449
|
+
/** Start line (1-indexed) */
|
|
450
|
+
startLine: number;
|
|
451
|
+
/** End line (1-indexed, inclusive) */
|
|
452
|
+
endLine: number;
|
|
453
|
+
/** Raw content of the chunk */
|
|
454
|
+
content: string;
|
|
455
|
+
/** Language identifier */
|
|
456
|
+
language: string;
|
|
457
|
+
}
|
|
458
|
+
interface GitCommitRecord {
|
|
459
|
+
id?: number;
|
|
460
|
+
hash: string;
|
|
461
|
+
shortHash: string;
|
|
462
|
+
message: string;
|
|
463
|
+
author: string;
|
|
464
|
+
date: string;
|
|
465
|
+
timestamp: number;
|
|
466
|
+
filesChanged: string[];
|
|
467
|
+
diff?: string;
|
|
468
|
+
additions: number;
|
|
469
|
+
deletions: number;
|
|
470
|
+
isMerge: boolean;
|
|
471
|
+
}
|
|
472
|
+
type SearchResultType = 'code' | 'commit' | 'document' | 'collection';
|
|
473
|
+
interface CodeResultMetadata {
|
|
474
|
+
/** Database chunk ID (used by call graph annotations). */
|
|
475
|
+
id?: number;
|
|
476
|
+
/** File path (may duplicate CodeResult.filePath for metadata-only access). */
|
|
477
|
+
filePath?: string;
|
|
478
|
+
/** Adjacent chunk IDs from the same file (used by context expansion). */
|
|
479
|
+
chunkIds?: number[];
|
|
480
|
+
chunkType: string;
|
|
481
|
+
name?: string;
|
|
482
|
+
startLine: number;
|
|
483
|
+
endLine: number;
|
|
484
|
+
language: string;
|
|
485
|
+
searchType?: string;
|
|
486
|
+
rrfScore?: number;
|
|
487
|
+
}
|
|
488
|
+
interface CommitResultMetadata {
|
|
489
|
+
hash: string;
|
|
490
|
+
shortHash: string;
|
|
491
|
+
author: string;
|
|
492
|
+
date: string;
|
|
493
|
+
files: string[];
|
|
494
|
+
additions?: number;
|
|
495
|
+
deletions?: number;
|
|
496
|
+
diff?: string;
|
|
497
|
+
searchType?: string;
|
|
498
|
+
rrfScore?: number;
|
|
499
|
+
}
|
|
500
|
+
interface DocumentResultMetadata {
|
|
501
|
+
collection?: string;
|
|
502
|
+
title?: string;
|
|
503
|
+
seq?: number;
|
|
504
|
+
path?: string;
|
|
505
|
+
searchType?: string;
|
|
506
|
+
/** Internal chunk ID used by hybrid search to map fused results. */
|
|
507
|
+
chunkId?: number;
|
|
508
|
+
rrfScore?: number;
|
|
509
|
+
}
|
|
510
|
+
interface CodeResult {
|
|
511
|
+
type: 'code';
|
|
512
|
+
score: number;
|
|
513
|
+
filePath: string;
|
|
514
|
+
content: string;
|
|
515
|
+
context?: string;
|
|
516
|
+
metadata: CodeResultMetadata;
|
|
517
|
+
}
|
|
518
|
+
interface CommitResult {
|
|
519
|
+
type: 'commit';
|
|
520
|
+
score: number;
|
|
521
|
+
filePath?: string;
|
|
522
|
+
content: string;
|
|
523
|
+
context?: string;
|
|
524
|
+
metadata: CommitResultMetadata;
|
|
525
|
+
}
|
|
526
|
+
interface DocumentResult {
|
|
527
|
+
type: 'document';
|
|
528
|
+
score: number;
|
|
529
|
+
filePath: string;
|
|
530
|
+
content: string;
|
|
531
|
+
context?: string;
|
|
532
|
+
metadata: DocumentResultMetadata;
|
|
533
|
+
}
|
|
534
|
+
interface CollectionResultMetadata {
|
|
535
|
+
id?: number;
|
|
536
|
+
collection?: string;
|
|
537
|
+
rrfScore?: number;
|
|
538
|
+
[key: string]: unknown;
|
|
539
|
+
}
|
|
540
|
+
interface CollectionResult {
|
|
541
|
+
type: 'collection';
|
|
542
|
+
score: number;
|
|
543
|
+
filePath?: string;
|
|
544
|
+
content: string;
|
|
545
|
+
context?: string;
|
|
546
|
+
metadata: CollectionResultMetadata;
|
|
547
|
+
}
|
|
548
|
+
type SearchResult = CodeResult | CommitResult | DocumentResult | CollectionResult;
|
|
549
|
+
/** Narrow a SearchResult to CodeResult. */
|
|
550
|
+
declare function isCodeResult(r: SearchResult): r is CodeResult;
|
|
551
|
+
/** Narrow a SearchResult to CommitResult. */
|
|
552
|
+
declare function isCommitResult(r: SearchResult): r is CommitResult;
|
|
553
|
+
/** Narrow a SearchResult to DocumentResult. */
|
|
554
|
+
declare function isDocumentResult(r: SearchResult): r is DocumentResult;
|
|
555
|
+
/** Narrow a SearchResult to CollectionResult. */
|
|
556
|
+
declare function isCollectionResult(r: SearchResult): r is CollectionResult;
|
|
557
|
+
type MatchHandlers<T> = {
|
|
558
|
+
code?: (r: CodeResult) => T;
|
|
559
|
+
commit?: (r: CommitResult) => T;
|
|
560
|
+
document?: (r: DocumentResult) => T;
|
|
561
|
+
collection?: (r: CollectionResult) => T;
|
|
562
|
+
_?: (r: SearchResult) => T;
|
|
563
|
+
};
|
|
564
|
+
/**
|
|
565
|
+
* Pattern-match on SearchResult type. Calls the matching handler
|
|
566
|
+
* or the `_` fallback. Returns undefined if no handler matches.
|
|
567
|
+
*/
|
|
568
|
+
declare function matchResult<T>(result: SearchResult, handlers: MatchHandlers<T>): T | undefined;
|
|
569
|
+
interface ContextOptions {
|
|
570
|
+
/** Per-source result limits. Built-in: 'code', 'git'. Default: { code: 6, git: 5 } */
|
|
571
|
+
sources?: Record<string, number>;
|
|
572
|
+
/** Files the agent is about to modify (improves co-edit suggestions) */
|
|
573
|
+
affectedFiles?: string[];
|
|
574
|
+
/** Minimum similarity score threshold. Default: 0.25 */
|
|
575
|
+
minScore?: number;
|
|
576
|
+
/** Use MMR for diversity. Default: true */
|
|
577
|
+
useMMR?: boolean;
|
|
578
|
+
/** MMR lambda (0 = diversity, 1 = relevance). Default: 0.7 */
|
|
579
|
+
mmrLambda?: number;
|
|
580
|
+
/** Filter results to files under these path prefixes (e.g. 'src/services/' or ['src/', 'lib/']). */
|
|
581
|
+
pathPrefix?: string | string[];
|
|
582
|
+
/** Exclude results whose filePath starts with any of these prefixes (e.g. ['src/tests/', 'src/mocks/']). */
|
|
583
|
+
ignorePaths?: string[];
|
|
584
|
+
/** File paths to exclude from results (e.g. files already returned in a previous query). */
|
|
585
|
+
excludeFiles?: Set<string>;
|
|
586
|
+
/** Optional per-request pruner override (e.g. HaikuPruner for LLM noise filtering). */
|
|
587
|
+
pruner?: Pruner;
|
|
588
|
+
/** Caller origin for debug logging. */
|
|
589
|
+
source?: 'cli' | 'mcp' | 'daemon' | 'api';
|
|
590
|
+
/**
|
|
591
|
+
* Context field overrides. Merged on top of config.json "context" defaults.
|
|
592
|
+
* Plugin-defined fields like `lines`, `callTree`, `symbols`, `compact`, `imports`.
|
|
593
|
+
* Example: `{ lines: true, callTree: { depth: 3 }, symbols: true }`
|
|
594
|
+
*/
|
|
595
|
+
fields?: Record<string, unknown>;
|
|
596
|
+
}
|
|
597
|
+
interface DocumentCollection {
|
|
598
|
+
/** Collection name (e.g. 'notes', 'docs') */
|
|
599
|
+
name: string;
|
|
600
|
+
/** Directory path to index */
|
|
601
|
+
path: string;
|
|
602
|
+
/** Glob pattern for files (default: all markdown) */
|
|
603
|
+
pattern?: string;
|
|
604
|
+
/** Glob patterns to ignore */
|
|
605
|
+
ignore?: string[];
|
|
606
|
+
/** Context description for this collection */
|
|
607
|
+
context?: string;
|
|
608
|
+
}
|
|
609
|
+
interface DocChunk {
|
|
610
|
+
id?: number;
|
|
611
|
+
/** Collection name */
|
|
612
|
+
collection: string;
|
|
613
|
+
/** Relative file path within the collection */
|
|
614
|
+
filePath: string;
|
|
615
|
+
/** Document title (first heading or filename) */
|
|
616
|
+
title: string;
|
|
617
|
+
/** Chunk content */
|
|
618
|
+
content: string;
|
|
619
|
+
/** Chunk sequence within the document (0, 1, 2...) */
|
|
620
|
+
seq: number;
|
|
621
|
+
/** Character position in original document */
|
|
622
|
+
pos: number;
|
|
623
|
+
/** Content hash for incremental updates */
|
|
624
|
+
contentHash: string;
|
|
625
|
+
}
|
|
626
|
+
/** File-level progress (used by indexers). */
|
|
627
|
+
type ProgressCallback = (file: string, current: number, total: number) => void;
|
|
628
|
+
/** Stage-level progress (used by BrainBank.index() orchestrator). */
|
|
629
|
+
type StageProgressCallback = (stage: string, message: string) => void;
|
|
630
|
+
interface IndexResult {
|
|
631
|
+
indexed: number;
|
|
632
|
+
skipped: number;
|
|
633
|
+
chunks?: number;
|
|
634
|
+
removed?: number;
|
|
635
|
+
}
|
|
636
|
+
interface CoEditSuggestion {
|
|
637
|
+
file: string;
|
|
638
|
+
count: number;
|
|
639
|
+
}
|
|
640
|
+
/** Generalized watch event — works for files, APIs, webhooks. */
|
|
641
|
+
interface WatchEvent {
|
|
642
|
+
/** Event type. 'sync' is for batch/poll sources that don't distinguish CRUD. */
|
|
643
|
+
type: 'create' | 'update' | 'delete' | 'sync';
|
|
644
|
+
/** Unique ID of the changed item (file path, PR#123, PROJ-456, etc.). */
|
|
645
|
+
sourceId: string;
|
|
646
|
+
/** Source descriptor (e.g. 'file', 'github:pr', 'jira:card'). */
|
|
647
|
+
sourceName: string;
|
|
648
|
+
/** Optional raw payload to avoid re-fetching. */
|
|
649
|
+
payload?: unknown;
|
|
650
|
+
}
|
|
651
|
+
/** Callback that plugins invoke when they detect a change. */
|
|
652
|
+
type WatchEventHandler = (event: WatchEvent) => void;
|
|
653
|
+
/** Lifecycle handle returned by WatchablePlugin.watch(). */
|
|
654
|
+
interface WatchHandle {
|
|
655
|
+
/** Stop watching and release resources. */
|
|
656
|
+
stop(): Promise<void>;
|
|
657
|
+
/** Whether the watcher is still active. */
|
|
658
|
+
readonly active: boolean;
|
|
659
|
+
}
|
|
660
|
+
/** Optional hints from plugin to core — debounce, batching, priority. */
|
|
661
|
+
interface WatchConfig {
|
|
662
|
+
/** Debounce interval in ms. 0 = process immediately. Default: inherited from WatchOptions. */
|
|
663
|
+
debounceMs?: number;
|
|
664
|
+
/** Max events to batch before triggering re-index. Default: unlimited. */
|
|
665
|
+
batchSize?: number;
|
|
666
|
+
/** Processing priority. Default: 'realtime'. */
|
|
667
|
+
priority?: 'realtime' | 'background';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* BrainBank — HNSW Vector Index
|
|
672
|
+
*
|
|
673
|
+
* Wraps hnswlib-node for O(log n) approximate nearest neighbor search.
|
|
674
|
+
* M=16 connections, ef=200 construction, ef=50 search by default.
|
|
675
|
+
* 150x faster than brute force at 1M vectors.
|
|
676
|
+
*
|
|
677
|
+
* Supports disk persistence: save(path) / tryLoad(path, count)
|
|
678
|
+
* to skip costly vector-by-vector rebuild on startup.
|
|
679
|
+
*/
|
|
680
|
+
|
|
681
|
+
declare class HNSWIndex implements VectorIndex {
|
|
682
|
+
private _dims;
|
|
683
|
+
private _maxElements;
|
|
684
|
+
private _M;
|
|
685
|
+
private _efConstruction;
|
|
686
|
+
private _efSearch;
|
|
687
|
+
private _index;
|
|
688
|
+
private _lib;
|
|
689
|
+
private _ids;
|
|
690
|
+
constructor(_dims: number, _maxElements?: number, _M?: number, _efConstruction?: number, _efSearch?: number);
|
|
691
|
+
/**
|
|
692
|
+
* Initialize the HNSW index.
|
|
693
|
+
* Must be called before add/search.
|
|
694
|
+
*/
|
|
695
|
+
init(): Promise<this>;
|
|
696
|
+
/**
|
|
697
|
+
* Reinitialize the index in-place, clearing all vectors.
|
|
698
|
+
* Required after reembed or full re-index to avoid duplicate IDs.
|
|
699
|
+
* init() must have been called first.
|
|
700
|
+
*/
|
|
701
|
+
reinit(): void;
|
|
702
|
+
private _createIndex;
|
|
703
|
+
/** Maximum capacity of this index. */
|
|
704
|
+
get maxElements(): number;
|
|
705
|
+
/**
|
|
706
|
+
* Add a vector with an integer ID.
|
|
707
|
+
* The vector should be pre-normalized for cosine distance.
|
|
708
|
+
*/
|
|
709
|
+
add(vector: Float32Array, id: number): void;
|
|
710
|
+
/**
|
|
711
|
+
* Mark a vector as deleted so it no longer appears in searches.
|
|
712
|
+
* Uses hnswlib-node markDelete under the hood.
|
|
713
|
+
* Safe to call with an ID that doesn't exist.
|
|
714
|
+
*/
|
|
715
|
+
remove(id: number): void;
|
|
716
|
+
/**
|
|
717
|
+
* Search for the k nearest neighbors.
|
|
718
|
+
* Returns results sorted by score (highest first).
|
|
719
|
+
* Score is 1 - cosine_distance (1.0 = identical).
|
|
720
|
+
*/
|
|
721
|
+
search(query: Float32Array, k: number): SearchHit[];
|
|
722
|
+
/** Number of vectors in the index. */
|
|
723
|
+
get size(): number;
|
|
724
|
+
/**
|
|
725
|
+
* Save the HNSW graph to disk.
|
|
726
|
+
* The file can be loaded later with tryLoad() to skip vector-by-vector insertion.
|
|
727
|
+
*/
|
|
728
|
+
save(path: string): void;
|
|
729
|
+
/**
|
|
730
|
+
* Try to load a previously saved HNSW index from disk.
|
|
731
|
+
* Returns true if loaded successfully, false if stale or missing.
|
|
732
|
+
* @param path File path to the saved index
|
|
733
|
+
* @param expectedCount Expected number of vectors (from SQLite) — used to detect staleness
|
|
734
|
+
*/
|
|
735
|
+
tryLoad(path: string, expectedCount: number): boolean;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* BrainBank — Search Types
|
|
740
|
+
*
|
|
741
|
+
* Shared interface for all search strategies.
|
|
742
|
+
* Implement SearchStrategy to add a new search backend.
|
|
743
|
+
*/
|
|
744
|
+
|
|
745
|
+
/** Any search implementation follows this shape. */
|
|
746
|
+
interface SearchStrategy {
|
|
747
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
748
|
+
/** Rebuild internal indices (e.g. FTS5). Optional. */
|
|
749
|
+
rebuild?(): void;
|
|
750
|
+
}
|
|
751
|
+
/** Pre-embedded vector search for a single domain (code, git, etc.). */
|
|
752
|
+
interface DomainVectorSearch {
|
|
753
|
+
/** Search using a pre-computed query vector. Optional queryText enables BM25 fusion. */
|
|
754
|
+
search(queryVec: Float32Array, k: number, minScore: number, useMMR?: boolean, mmrLambda?: number, queryText?: string): SearchResult[];
|
|
755
|
+
}
|
|
756
|
+
interface SearchOptions {
|
|
757
|
+
/** Per-source result limits. Built-in: 'code', 'git', 'memory'. Any other key = custom plugin or KV collection. */
|
|
758
|
+
sources?: Record<string, number>;
|
|
759
|
+
/** Minimum similarity score. Default: 0.25 */
|
|
760
|
+
minScore?: number;
|
|
761
|
+
/** Use MMR for diversity. Default: true */
|
|
762
|
+
useMMR?: boolean;
|
|
763
|
+
/** MMR lambda. Default: 0.7 */
|
|
764
|
+
mmrLambda?: number;
|
|
765
|
+
/** Caller origin for debug logging. */
|
|
766
|
+
source?: 'cli' | 'mcp' | 'daemon' | 'api';
|
|
767
|
+
/** Filter results to files under these path prefixes. */
|
|
768
|
+
pathPrefix?: string | string[];
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* BrainBank — Webhook Server
|
|
773
|
+
*
|
|
774
|
+
* Optional shared HTTP server for push-based watch plugins (e.g. Jira, GitHub).
|
|
775
|
+
* Opt-in: only created when `new BrainBank({ webhookPort: 4242 })` is configured.
|
|
776
|
+
*
|
|
777
|
+
* Plugins register routes during `watch()`:
|
|
778
|
+
* ctx.webhookServer?.register('jira', '/jira/webhook', handler);
|
|
779
|
+
*
|
|
780
|
+
* Each plugin gets its own path namespace. Unregistering cleans up the route.
|
|
781
|
+
*/
|
|
782
|
+
/** Handler for incoming webhook payloads. */
|
|
783
|
+
type WebhookHandler = (body: unknown) => void;
|
|
784
|
+
/** Shared HTTP server for push-based watch plugins. */
|
|
785
|
+
declare class WebhookServer {
|
|
786
|
+
private _server;
|
|
787
|
+
private _routes;
|
|
788
|
+
private _listening;
|
|
789
|
+
/** Start listening on the specified port. */
|
|
790
|
+
listen(port: number): void;
|
|
791
|
+
/** Register a webhook route for a plugin. */
|
|
792
|
+
register(pluginName: string, path: string, handler: WebhookHandler): void;
|
|
793
|
+
/** Remove all routes for a plugin. */
|
|
794
|
+
unregister(pluginName: string): void;
|
|
795
|
+
/** Stop the server and clear all routes. */
|
|
796
|
+
close(): void;
|
|
797
|
+
/** Whether the server is currently listening. */
|
|
798
|
+
get active(): boolean;
|
|
799
|
+
/** Route incoming POST requests to the matching handler. */
|
|
800
|
+
private _handleRequest;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* BrainBank — Plugin System
|
|
805
|
+
*
|
|
806
|
+
* Plugins are pluggable strategies that scan external data sources
|
|
807
|
+
* and push content into BrainBank. Built-in plugins handle code,
|
|
808
|
+
* git, and docs. Third-party frameworks (LangChain, etc.)
|
|
809
|
+
* can implement custom plugins.
|
|
810
|
+
*
|
|
811
|
+
* import { BrainBank } from 'brainbank';
|
|
812
|
+
* import { code } from 'brainbank/indexers/code';
|
|
813
|
+
*
|
|
814
|
+
* const brain = new BrainBank()
|
|
815
|
+
* .use(code({ repoPath: '.' }));
|
|
816
|
+
*/
|
|
817
|
+
|
|
818
|
+
interface PluginContext {
|
|
819
|
+
/** Database adapter (shared across all plugins). */
|
|
820
|
+
db: DatabaseAdapter;
|
|
821
|
+
/** Embedding provider (shared). */
|
|
822
|
+
embedding: EmbeddingProvider;
|
|
823
|
+
/** Resolved BrainBank config. */
|
|
824
|
+
config: ResolvedConfig;
|
|
825
|
+
/**
|
|
826
|
+
* Create and initialize an HNSW index.
|
|
827
|
+
* Pass `name` to enable disk persistence (recommended).
|
|
828
|
+
*
|
|
829
|
+
* **Private vs shared:** Use `getOrCreateSharedHnsw()` for indexes that should be
|
|
830
|
+
* part of the composite search (code, git) and persisted across restarts.
|
|
831
|
+
* Use `createHnsw()` for plugin-local indexes that don't participate in the
|
|
832
|
+
* main search pipeline (e.g. internal similarity lookups).
|
|
833
|
+
*/
|
|
834
|
+
createHnsw(maxElements?: number, dims?: number, name?: string): Promise<HNSWIndex>;
|
|
835
|
+
/** Load existing vectors from a SQLite vectors table into an HNSW index + cache. */
|
|
836
|
+
loadVectors(table: string, idCol: string, hnsw: HNSWIndex, cache: Map<number, Float32Array>): void;
|
|
837
|
+
/**
|
|
838
|
+
* Get or create a shared HNSW index by key.
|
|
839
|
+
*
|
|
840
|
+
* **HNSW sharing strategies:**
|
|
841
|
+
* The `type` key determines sharing behavior. Two plugins that pass the
|
|
842
|
+
* same key share one HNSW index; different keys get separate indexes.
|
|
843
|
+
*
|
|
844
|
+
* | Plugin type | Key passed | Sharing behavior |
|
|
845
|
+
* |------------|------------------|------------------------------------------|
|
|
846
|
+
* | git | `'git'` | All `git:*` repos share one HNSW |
|
|
847
|
+
* | docs | `'docs'` | All docs share one HNSW |
|
|
848
|
+
* | code | `this.name` | Each `code:*` repo gets its own HNSW |
|
|
849
|
+
*
|
|
850
|
+
* **Rule of thumb:**
|
|
851
|
+
* - Same key = shared index (saves memory, single search covers all)
|
|
852
|
+
* - Plugin name as key = per-repo index (avoids cross-repo noise)
|
|
853
|
+
*
|
|
854
|
+
* The key is also used for hot-reload (`ensureFresh`) and disk persistence
|
|
855
|
+
* (`hnsw-<key>.index`), so it must match the key used in `bumpVersion()`.
|
|
856
|
+
*/
|
|
857
|
+
getOrCreateSharedHnsw(type: string, maxElements?: number, dims?: number): Promise<{
|
|
858
|
+
hnsw: HNSWIndex;
|
|
859
|
+
vecCache: Map<number, Float32Array>;
|
|
860
|
+
isNew: boolean;
|
|
861
|
+
}>;
|
|
862
|
+
/** Get or create a dynamic collection. */
|
|
863
|
+
collection(name: string): ICollection;
|
|
864
|
+
/**
|
|
865
|
+
* Create an incremental tracker scoped to this plugin.
|
|
866
|
+
* Provides `isUnchanged`, `markIndexed`, `findOrphans`, `remove`, `clear`
|
|
867
|
+
* for standardized add/update/delete detection during indexing.
|
|
868
|
+
*/
|
|
869
|
+
createTracker(): IncrementalTracker;
|
|
870
|
+
/** Optional webhook server for push-based watch plugins. undefined if not configured. */
|
|
871
|
+
webhookServer?: WebhookServer;
|
|
872
|
+
}
|
|
873
|
+
interface Plugin {
|
|
874
|
+
/** Unique plugin name (e.g. 'code', 'git', 'docs'). */
|
|
875
|
+
readonly name: string;
|
|
876
|
+
/** Initialize the plugin (create HNSW, load vectors, etc.). */
|
|
877
|
+
initialize(ctx: PluginContext): Promise<void>;
|
|
878
|
+
/** Return stats for this plugin. */
|
|
879
|
+
stats?(): Record<string, number | string>;
|
|
880
|
+
/** Clean up resources. */
|
|
881
|
+
close?(): void;
|
|
882
|
+
}
|
|
883
|
+
/** Options accepted by IndexablePlugin.index(). */
|
|
884
|
+
interface IndexOptions {
|
|
885
|
+
forceReindex?: boolean;
|
|
886
|
+
depth?: number;
|
|
887
|
+
onProgress?: ProgressCallback;
|
|
888
|
+
}
|
|
889
|
+
/** Plugins that can scan and index content (code, git). */
|
|
890
|
+
interface IndexablePlugin extends Plugin {
|
|
891
|
+
index(options?: IndexOptions): Promise<IndexResult>;
|
|
892
|
+
/** Incremental: re-index only specific items by ID. Falls back to index() if not implemented. */
|
|
893
|
+
indexItems?(ids: string[]): Promise<IndexResult>;
|
|
894
|
+
}
|
|
895
|
+
/** Plugins that can search indexed content (docs). */
|
|
896
|
+
interface SearchablePlugin extends Plugin {
|
|
897
|
+
search(query: string, options?: Record<string, unknown>): Promise<SearchResult[]>;
|
|
898
|
+
}
|
|
899
|
+
/** Plugins that can watch their own data source for changes. */
|
|
900
|
+
interface WatchablePlugin extends Plugin {
|
|
901
|
+
/** Start watching. Plugin controls how (fs.watch, polling, webhook, etc.). */
|
|
902
|
+
watch(onEvent: WatchEventHandler): WatchHandle;
|
|
903
|
+
/** Optional hints for the core (debounce, batching, priority). */
|
|
904
|
+
watchConfig?(): WatchConfig;
|
|
905
|
+
}
|
|
906
|
+
/** Check if a plugin can scan/index content. */
|
|
907
|
+
declare function isIndexable(i: Plugin): i is IndexablePlugin;
|
|
908
|
+
/** Check if a plugin can search content. */
|
|
909
|
+
declare function isSearchable(i: Plugin): i is SearchablePlugin;
|
|
910
|
+
/** Check if a plugin can watch its own data source. */
|
|
911
|
+
declare function isWatchable(i: Plugin): i is WatchablePlugin;
|
|
912
|
+
/** Path-specific context metadata for document collections. */
|
|
913
|
+
interface PathContext {
|
|
914
|
+
collection: string;
|
|
915
|
+
path: string;
|
|
916
|
+
context: string;
|
|
917
|
+
}
|
|
918
|
+
/** Plugins that manage document collections (docs). */
|
|
919
|
+
interface DocsPlugin extends SearchablePlugin {
|
|
920
|
+
addCollection(collection: DocumentCollection): void;
|
|
921
|
+
removeCollection(name: string): void;
|
|
922
|
+
listCollections(): DocumentCollection[];
|
|
923
|
+
indexDocs(options?: {
|
|
924
|
+
onProgress?: (collection: string, file: string, current: number, total: number) => void;
|
|
925
|
+
}): Promise<Record<string, {
|
|
926
|
+
indexed: number;
|
|
927
|
+
skipped: number;
|
|
928
|
+
removed: number;
|
|
929
|
+
chunks: number;
|
|
930
|
+
}>>;
|
|
931
|
+
addContext(collection: string, path: string, context: string): void;
|
|
932
|
+
listContexts(): PathContext[];
|
|
933
|
+
}
|
|
934
|
+
/** Check if a plugin manages document collections. */
|
|
935
|
+
declare function isDocsPlugin(i: Plugin): i is DocsPlugin;
|
|
936
|
+
/** Plugin that provides co-edit suggestions (e.g. git). */
|
|
937
|
+
interface CoEditPlugin extends Plugin {
|
|
938
|
+
coEdits: {
|
|
939
|
+
suggest(filePath: string, limit: number): {
|
|
940
|
+
file: string;
|
|
941
|
+
count: number;
|
|
942
|
+
}[];
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/** Check if a plugin provides co-edit suggestions. */
|
|
946
|
+
declare function isCoEditPlugin(p: Plugin): p is CoEditPlugin;
|
|
947
|
+
/** Table descriptor for re-embedding — maps text rows to vector BLOBs. */
|
|
948
|
+
interface ReembedTable {
|
|
949
|
+
/** Human-readable name (for progress). */
|
|
950
|
+
name: string;
|
|
951
|
+
/** Table with text content. */
|
|
952
|
+
textTable: string;
|
|
953
|
+
/** Table with vector BLOBs. */
|
|
954
|
+
vectorTable: string;
|
|
955
|
+
/** PK column in text table. */
|
|
956
|
+
idColumn: string;
|
|
957
|
+
/** FK column in vector table. */
|
|
958
|
+
fkColumn: string;
|
|
959
|
+
/** Build the embedding text from a DB row. */
|
|
960
|
+
textBuilder: (row: Record<string, unknown>) => string;
|
|
961
|
+
}
|
|
962
|
+
/** Plugins that own vector tables and can rebuild embedding text from DB rows. */
|
|
963
|
+
interface ReembeddablePlugin extends Plugin {
|
|
964
|
+
/** Table descriptor for re-embedding. */
|
|
965
|
+
reembedConfig(): ReembedTable;
|
|
966
|
+
}
|
|
967
|
+
/** Check if a plugin supports re-embedding. */
|
|
968
|
+
declare function isReembeddable(p: Plugin): p is ReembeddablePlugin;
|
|
969
|
+
/** Plugin that provides a domain-specific vector search strategy. */
|
|
970
|
+
interface VectorSearchPlugin extends Plugin {
|
|
971
|
+
/** Create the domain vector search (called during SearchAPI wiring). */
|
|
972
|
+
createVectorSearch(): DomainVectorSearch | undefined;
|
|
973
|
+
}
|
|
974
|
+
/** Check if a plugin provides a domain vector search. */
|
|
975
|
+
declare function isVectorSearchPlugin(p: Plugin): p is VectorSearchPlugin;
|
|
976
|
+
/** Describes a configurable context field that a plugin supports. */
|
|
977
|
+
interface ContextFieldDef {
|
|
978
|
+
/** Field name (e.g. 'lines', 'callTree', 'symbols'). Must be unique per plugin. */
|
|
979
|
+
name: string;
|
|
980
|
+
/** Accepted value type. 'object' allows nested config like `{ depth: 3 }`. */
|
|
981
|
+
type: 'boolean' | 'number' | 'object';
|
|
982
|
+
/** Default value (used when not specified in config or query). */
|
|
983
|
+
default: unknown;
|
|
984
|
+
/** Human-readable description for CLI --help and MCP tool descriptions. */
|
|
985
|
+
description: string;
|
|
986
|
+
}
|
|
987
|
+
/** Plugin that declares configurable context fields. */
|
|
988
|
+
interface ContextFieldPlugin extends Plugin {
|
|
989
|
+
/** Declare available context fields. Called once during setup. */
|
|
990
|
+
contextFields(): ContextFieldDef[];
|
|
991
|
+
}
|
|
992
|
+
/** Check if a plugin declares context fields. */
|
|
993
|
+
declare function isContextFieldPlugin(p: Plugin): p is ContextFieldPlugin;
|
|
994
|
+
/** Plugin that contributes sections to the context builder output. */
|
|
995
|
+
interface ContextFormatterPlugin extends Plugin {
|
|
996
|
+
/**
|
|
997
|
+
* Append formatted markdown sections to `parts`.
|
|
998
|
+
* `fields` contains resolved context fields (plugin defaults ← config ← per-query).
|
|
999
|
+
*/
|
|
1000
|
+
formatContext(results: SearchResult[], parts: string[], fields: Record<string, unknown>): void;
|
|
1001
|
+
}
|
|
1002
|
+
/** Check if a plugin provides context formatting. */
|
|
1003
|
+
declare function isContextFormatterPlugin(p: Plugin): p is ContextFormatterPlugin;
|
|
1004
|
+
/** Plugin that owns database tables and supports versioned migrations. */
|
|
1005
|
+
interface MigratablePlugin extends Plugin {
|
|
1006
|
+
/** Current schema version for this plugin. */
|
|
1007
|
+
readonly schemaVersion: number;
|
|
1008
|
+
/** Ordered list of migrations (version 1, 2, 3, …). */
|
|
1009
|
+
readonly migrations: Migration[];
|
|
1010
|
+
}
|
|
1011
|
+
/** Check if a plugin supports schema migrations. */
|
|
1012
|
+
declare function isMigratable(p: Plugin): p is MigratablePlugin;
|
|
1013
|
+
/** Plugin that can do FTS5 keyword search on its own tables. */
|
|
1014
|
+
interface BM25SearchPlugin extends Plugin {
|
|
1015
|
+
/** Run BM25 keyword search. Returns scored results. */
|
|
1016
|
+
searchBM25(query: string, k: number, minScore?: number): SearchResult[];
|
|
1017
|
+
/** Rebuild the FTS5 index from the content table. */
|
|
1018
|
+
rebuildFTS?(): void;
|
|
1019
|
+
}
|
|
1020
|
+
/** Check if a plugin provides BM25 keyword search. */
|
|
1021
|
+
declare function isBM25SearchPlugin(p: Plugin): p is BM25SearchPlugin;
|
|
1022
|
+
/** Plugin that supports context expansion (provides manifest + resolves chunk IDs). */
|
|
1023
|
+
interface ExpandablePlugin extends Plugin {
|
|
1024
|
+
/**
|
|
1025
|
+
* Build a manifest of candidate chunks for LLM expansion.
|
|
1026
|
+
* Returns chunks from files NOT already in search results.
|
|
1027
|
+
* Priority chunks (from import graph neighbors) are marked with `priority: true`.
|
|
1028
|
+
*
|
|
1029
|
+
* @param excludeFilePaths File paths already present in search results — excluded from manifest.
|
|
1030
|
+
* @param excludeIds Chunk IDs already in search results — excluded from manifest.
|
|
1031
|
+
* @param resultFilePaths File paths in search results — used to query import graph for priority chunks.
|
|
1032
|
+
*/
|
|
1033
|
+
buildManifest(excludeFilePaths: string[], excludeIds: number[], resultFilePaths?: string[]): ExpanderManifestItem[];
|
|
1034
|
+
/**
|
|
1035
|
+
* Resolve chunk IDs back into SearchResults.
|
|
1036
|
+
* Called after the expander selects additional IDs.
|
|
1037
|
+
*/
|
|
1038
|
+
resolveChunks(ids: number[]): SearchResult[];
|
|
1039
|
+
}
|
|
1040
|
+
/** Check if a plugin supports context expansion. */
|
|
1041
|
+
declare function isExpandablePlugin(p: Plugin): p is ExpandablePlugin;
|
|
1042
|
+
/** Plugin that can resolve file paths/patterns directly to SearchResults (no search). */
|
|
1043
|
+
interface FileResolvablePlugin extends Plugin {
|
|
1044
|
+
/**
|
|
1045
|
+
* Resolve file paths, directories, and glob patterns to SearchResults.
|
|
1046
|
+
* Each entry is resolved: exact → directory → glob → fuzzy basename fallback.
|
|
1047
|
+
*
|
|
1048
|
+
* @param patterns - File paths, directory prefixes (trailing `/`), or glob patterns (`*`).
|
|
1049
|
+
*/
|
|
1050
|
+
resolveFiles(patterns: string[]): SearchResult[];
|
|
1051
|
+
}
|
|
1052
|
+
/** Check if a plugin can resolve files directly. */
|
|
1053
|
+
declare function isFileResolvable(p: Plugin): p is FileResolvablePlugin;
|
|
1054
|
+
/**
|
|
1055
|
+
* Scan info for the TUI sidebar. Describes what content a plugin can index.
|
|
1056
|
+
* Exported standalone by plugin packages — called BEFORE plugin initialization.
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* ```typescript
|
|
1060
|
+
* // brainbank-csv/index.ts
|
|
1061
|
+
* export function scan(repoPath: string): PluginScanInfo { ... }
|
|
1062
|
+
* ```
|
|
1063
|
+
*/
|
|
1064
|
+
interface PluginScanInfo {
|
|
1065
|
+
/** Plugin name (e.g. 'csv', 'openapi'). */
|
|
1066
|
+
name: string;
|
|
1067
|
+
/** Whether there's content available to index. */
|
|
1068
|
+
available: boolean;
|
|
1069
|
+
/** Human-readable summary (e.g. '12 CSV files'). */
|
|
1070
|
+
summary: string;
|
|
1071
|
+
/** Emoji icon for TUI display. */
|
|
1072
|
+
icon: string;
|
|
1073
|
+
/** Whether checked by default in the module selector. */
|
|
1074
|
+
checked: boolean;
|
|
1075
|
+
/** Reason this plugin is disabled (shown when unavailable). */
|
|
1076
|
+
disabled?: string;
|
|
1077
|
+
/** Detail lines for the scan tree (e.g. per-file breakdown). */
|
|
1078
|
+
details?: string[];
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* A single line in the TUI explorer preview panel.
|
|
1082
|
+
* Returned by `preview()` — rendered as-is in the right panel.
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```typescript
|
|
1086
|
+
* // brainbank-csv/index.ts
|
|
1087
|
+
* export function preview(repoPath: string): PluginPreviewLine[] {
|
|
1088
|
+
* return [
|
|
1089
|
+
* { text: '📊 3 CSV files', bold: true },
|
|
1090
|
+
* { text: ' sales.csv 2.3 MB', color: '#9ECE6A' },
|
|
1091
|
+
* ];
|
|
1092
|
+
* }
|
|
1093
|
+
* ```
|
|
1094
|
+
*/
|
|
1095
|
+
interface PluginPreviewLine {
|
|
1096
|
+
/** Text content for this line. */
|
|
1097
|
+
text: string;
|
|
1098
|
+
/** Optional hex color (e.g. '#9ECE6A'). */
|
|
1099
|
+
color?: string;
|
|
1100
|
+
/** Render bold. */
|
|
1101
|
+
bold?: boolean;
|
|
1102
|
+
/** Render dimmed. */
|
|
1103
|
+
dim?: boolean;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* BrainBank — Re-embedding Engine
|
|
1108
|
+
*
|
|
1109
|
+
* Regenerates all vectors without re-indexing.
|
|
1110
|
+
* Reads existing text from SQLite, embeds with the current provider,
|
|
1111
|
+
* and replaces vector BLOBs. No file I/O, no git parsing, no re-chunking.
|
|
1112
|
+
*
|
|
1113
|
+
* Usage:
|
|
1114
|
+
* const result = await brain.reembed({ onProgress });
|
|
1115
|
+
* // → { code: 1200, git: 500, docs: 80, kv: 45, total: 1837 }
|
|
1116
|
+
*/
|
|
1117
|
+
|
|
1118
|
+
interface ReembedResult {
|
|
1119
|
+
/** Per-table vector counts. Keys are table names (e.g. 'code', 'git', 'docs', 'kv'). */
|
|
1120
|
+
counts: Record<string, number>;
|
|
1121
|
+
total: number;
|
|
1122
|
+
}
|
|
1123
|
+
interface ReembedOptions {
|
|
1124
|
+
/** Progress callback: (tableName, current, total) */
|
|
1125
|
+
onProgress?: ProgressCallback;
|
|
1126
|
+
/** Batch size for embedBatch. Default: 50 */
|
|
1127
|
+
batchSize?: number;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* BrainBank — Watcher
|
|
1132
|
+
*
|
|
1133
|
+
* Thin coordinator for plugin-driven watching. Each plugin CAN drive its own
|
|
1134
|
+
* watching via WatchablePlugin.watch(). For IndexablePlugins that don't
|
|
1135
|
+
* implement WatchablePlugin, the Watcher provides a single shared fs.watch
|
|
1136
|
+
* tree with fan-out routing so each plugin only receives relevant events.
|
|
1137
|
+
*
|
|
1138
|
+
* Responsibilities:
|
|
1139
|
+
* 1. Call `plugin.watch(onEvent)` for each WatchablePlugin
|
|
1140
|
+
* 2. For IndexablePlugins without watch(), share one recursive fs.watch tree
|
|
1141
|
+
* 3. Route events to the correct plugin based on sub-repo scope
|
|
1142
|
+
* 4. Dedup macOS double-fire events (change+rename per save)
|
|
1143
|
+
* 5. Apply per-plugin debounce from `plugin.watchConfig()`
|
|
1144
|
+
* 6. On event: call `plugin.indexItems([id])` or `plugin.index()` for re-indexing
|
|
1145
|
+
* 7. Call `handle.stop()` on `close()`
|
|
1146
|
+
*
|
|
1147
|
+
* const watcher = brain.watch({ debounceMs: 2000 });
|
|
1148
|
+
* watcher.close(); // stop watching
|
|
1149
|
+
*/
|
|
1150
|
+
|
|
1151
|
+
interface WatchOptions {
|
|
1152
|
+
/** Default debounce for plugins that don't specify watchConfig. Default: 2000 */
|
|
1153
|
+
debounceMs?: number;
|
|
1154
|
+
/** Glob patterns to ignore (from config.json code.ignore). */
|
|
1155
|
+
ignore?: string[];
|
|
1156
|
+
/** Called when a source triggers re-indexing. */
|
|
1157
|
+
onIndex?: (sourceId: string, pluginName: string) => void;
|
|
1158
|
+
/** Called on errors. */
|
|
1159
|
+
onError?: (error: Error) => void;
|
|
1160
|
+
}
|
|
1161
|
+
/** Plugin-driven watcher that coordinates re-indexing across all WatchablePlugins. */
|
|
1162
|
+
declare class Watcher {
|
|
1163
|
+
private _active;
|
|
1164
|
+
private _batches;
|
|
1165
|
+
private _reindexFn;
|
|
1166
|
+
private _options;
|
|
1167
|
+
private _keepalive;
|
|
1168
|
+
constructor(reindexFn: () => Promise<void>, plugins: Plugin[], options?: WatchOptions, repoPath?: string);
|
|
1169
|
+
/** Whether the watcher is active. */
|
|
1170
|
+
get active(): boolean;
|
|
1171
|
+
/** Stop all plugin watchers. */
|
|
1172
|
+
close(): Promise<void>;
|
|
1173
|
+
/** Start watching for each WatchablePlugin, with shared fs.watch fallback. */
|
|
1174
|
+
private _startWatching;
|
|
1175
|
+
/**
|
|
1176
|
+
* Single shared recursive fs.watch that fans out events to multiple plugins.
|
|
1177
|
+
* Each event is routed based on file extension (docs → .md only, code → isSupported).
|
|
1178
|
+
*/
|
|
1179
|
+
private _startSharedFsWatch;
|
|
1180
|
+
/** Handle an incoming event from a plugin. */
|
|
1181
|
+
private _onEvent;
|
|
1182
|
+
/** Flush pending events for a plugin — trigger re-indexing. */
|
|
1183
|
+
private _flush;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* BrainBank — Main Orchestrator
|
|
1188
|
+
*
|
|
1189
|
+
* Thin facade that composes services:
|
|
1190
|
+
* - **PluginRegistry** — registration + lookup
|
|
1191
|
+
* - **SearchAPI** — all search + context logic
|
|
1192
|
+
* - **runIndex** — code / git / docs indexing orchestration
|
|
1193
|
+
*
|
|
1194
|
+
* Initialization is inline — no indirection layers.
|
|
1195
|
+
* All heavy logic lives in those modules; BrainBank owns state,
|
|
1196
|
+
* guards (`_requireInit` / `initialize`), and public API shape.
|
|
1197
|
+
*
|
|
1198
|
+
* Multi-process coordination:
|
|
1199
|
+
* - `ensureFresh()` detects stale HNSW indices via `index_state` table
|
|
1200
|
+
* - Hot-reloads from disk when another process updated the index
|
|
1201
|
+
* - Called implicitly before every search operation
|
|
1202
|
+
*/
|
|
1203
|
+
|
|
1204
|
+
declare class BrainBank extends EventEmitter {
|
|
1205
|
+
private _config;
|
|
1206
|
+
private _db;
|
|
1207
|
+
private _embedding;
|
|
1208
|
+
private _registry;
|
|
1209
|
+
private _searchAPI?;
|
|
1210
|
+
private _indexDeps?;
|
|
1211
|
+
private _kvService?;
|
|
1212
|
+
private _initialized;
|
|
1213
|
+
private _initPromise;
|
|
1214
|
+
private _watcher?;
|
|
1215
|
+
private _webhookServer?;
|
|
1216
|
+
private _sharedHnsw;
|
|
1217
|
+
private _repoDBs;
|
|
1218
|
+
private _loadedVersions;
|
|
1219
|
+
constructor(config?: BrainBankConfig);
|
|
1220
|
+
/** Whether the brainbank has been initialized. */
|
|
1221
|
+
get isInitialized(): boolean;
|
|
1222
|
+
/** The resolved configuration. */
|
|
1223
|
+
get config(): Readonly<ResolvedConfig>;
|
|
1224
|
+
/** All registered plugin names (insertion order). */
|
|
1225
|
+
get plugins(): string[];
|
|
1226
|
+
/**
|
|
1227
|
+
* Register a plugin. Chainable.
|
|
1228
|
+
*
|
|
1229
|
+
* @example
|
|
1230
|
+
* brain.use(code({ repoPath: '.' })).use(docs());
|
|
1231
|
+
*
|
|
1232
|
+
* @throws If called after `initialize()`.
|
|
1233
|
+
*/
|
|
1234
|
+
use(plugin: Plugin): this;
|
|
1235
|
+
/**
|
|
1236
|
+
* Check if a plugin is loaded.
|
|
1237
|
+
* Also matches type prefix (e.g. `'code'` matches `'code:frontend'`).
|
|
1238
|
+
*/
|
|
1239
|
+
has(name: string): boolean;
|
|
1240
|
+
/** Get a plugin instance by name. Returns `undefined` if not loaded. */
|
|
1241
|
+
plugin<T extends Plugin = Plugin>(name: string): T | undefined;
|
|
1242
|
+
/**
|
|
1243
|
+
* Initialize database, HNSW indices, and load existing vectors.
|
|
1244
|
+
* Automatically called by `index` / `search` methods if not yet initialized.
|
|
1245
|
+
* Concurrent calls are deduped via `_initPromise`.
|
|
1246
|
+
*
|
|
1247
|
+
* @param options.force - If `true`, skip vector load on dimension mismatch.
|
|
1248
|
+
*/
|
|
1249
|
+
initialize(options?: {
|
|
1250
|
+
force?: boolean;
|
|
1251
|
+
}): Promise<void>;
|
|
1252
|
+
/**
|
|
1253
|
+
* Estimated memory footprint of loaded HNSW indices (bytes).
|
|
1254
|
+
* Counts only vector data: `vectorCount × dims × 4`.
|
|
1255
|
+
* Returns 0 if not initialized.
|
|
1256
|
+
*/
|
|
1257
|
+
memoryHint(): number;
|
|
1258
|
+
/** Close database and release all resources. Synchronous. */
|
|
1259
|
+
close(): void;
|
|
1260
|
+
/**
|
|
1261
|
+
* Get or create a dynamic collection (universal KV primitive).
|
|
1262
|
+
*
|
|
1263
|
+
* @example
|
|
1264
|
+
* const errors = brain.collection('debug_errors');
|
|
1265
|
+
* await errors.add('Fixed null check', { file: 'api.ts' });
|
|
1266
|
+
* const hits = await errors.search('null pointer');
|
|
1267
|
+
*
|
|
1268
|
+
* @throws If not initialized.
|
|
1269
|
+
*/
|
|
1270
|
+
collection(name: string): ICollection;
|
|
1271
|
+
/** List all collection names that have data. */
|
|
1272
|
+
listCollectionNames(): string[];
|
|
1273
|
+
/** Delete a collection's data and evict from cache. */
|
|
1274
|
+
deleteCollection(name: string): void;
|
|
1275
|
+
/** Run indexing across selected modules. Auto-initializes. */
|
|
1276
|
+
index(options?: {
|
|
1277
|
+
modules?: string[];
|
|
1278
|
+
forceReindex?: boolean;
|
|
1279
|
+
onProgress?: StageProgressCallback;
|
|
1280
|
+
pluginOptions?: Record<string, unknown>;
|
|
1281
|
+
}): Promise<Record<string, unknown>>;
|
|
1282
|
+
/**
|
|
1283
|
+
* Detect stale HNSW indices and hot-reload from disk.
|
|
1284
|
+
* Called implicitly before every search operation.
|
|
1285
|
+
* Cost: one SQLite SELECT (~5μs on WAL mode).
|
|
1286
|
+
*/
|
|
1287
|
+
ensureFresh(): Promise<void>;
|
|
1288
|
+
/**
|
|
1289
|
+
* Semantic search across all loaded modules.
|
|
1290
|
+
* Scope via `sources: { code: 10, git: 0 }`.
|
|
1291
|
+
*/
|
|
1292
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
1293
|
+
/**
|
|
1294
|
+
* Hybrid search: vector + BM25 fused with Reciprocal Rank Fusion.
|
|
1295
|
+
* Scope via `sources: { code: 10, git: 5, docs: 3, myNotes: 5 }`.
|
|
1296
|
+
*/
|
|
1297
|
+
hybridSearch(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
1298
|
+
/** BM25 keyword search only (no embeddings needed). */
|
|
1299
|
+
searchBM25(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
1300
|
+
/** Build formatted context block for LLM system prompt injection. Auto-initializes. */
|
|
1301
|
+
getContext(task: string, options?: ContextOptions): Promise<string>;
|
|
1302
|
+
/**
|
|
1303
|
+
* Resolve file paths, directories, and glob patterns to full SearchResults.
|
|
1304
|
+
* Bypasses search entirely — reads directly from plugin indexes.
|
|
1305
|
+
*
|
|
1306
|
+
* @example
|
|
1307
|
+
* const files = brain.resolveFiles(['src/auth/login.ts', 'src/graph/']);
|
|
1308
|
+
*/
|
|
1309
|
+
resolveFiles(patterns: string[]): SearchResult[];
|
|
1310
|
+
/** Rebuild FTS5 indices. */
|
|
1311
|
+
rebuildFTS(): void;
|
|
1312
|
+
/** Get statistics for all loaded plugins. */
|
|
1313
|
+
stats(): Record<string, Record<string, number | string> | undefined>;
|
|
1314
|
+
/** Start watching for changes and auto-re-index. */
|
|
1315
|
+
watch(options?: WatchOptions): Watcher;
|
|
1316
|
+
/**
|
|
1317
|
+
* Re-embed all existing text with the current embedding provider.
|
|
1318
|
+
* Use after switching providers (e.g. Local → OpenAI).
|
|
1319
|
+
*/
|
|
1320
|
+
reembed(options?: ReembedOptions): Promise<ReembedResult>;
|
|
1321
|
+
/**
|
|
1322
|
+
* Linear 8-step initialization:
|
|
1323
|
+
* 1. Open database
|
|
1324
|
+
* 2. Resolve embedding provider
|
|
1325
|
+
* 3. Check dimension mismatch
|
|
1326
|
+
* 4. Create KV HNSW + KVService
|
|
1327
|
+
* 5. Load KV vectors
|
|
1328
|
+
* 6. Initialize plugins
|
|
1329
|
+
* 7. Persist HNSW indices
|
|
1330
|
+
* 8. Build SearchAPI + index deps
|
|
1331
|
+
*/
|
|
1332
|
+
private _runInitialize;
|
|
1333
|
+
/** Reset shared state after a failed `_runInitialize`. */
|
|
1334
|
+
private _cleanupAfterFailedInit;
|
|
1335
|
+
/** Resolve embedding: explicit config > stored DB key > local default. */
|
|
1336
|
+
private _resolveEmbedding;
|
|
1337
|
+
/**
|
|
1338
|
+
* Get or create a per-repo SQLiteAdapter for namespaced plugins.
|
|
1339
|
+
* Non-namespaced plugins use the root DB.
|
|
1340
|
+
* DB path: `.brainbank/<repoName>.db` (e.g., `servicehub-backend.db`).
|
|
1341
|
+
*/
|
|
1342
|
+
private _getOrCreatePluginDb;
|
|
1343
|
+
/** Build a per-plugin `PluginContext` with appropriate DB and HNSW scoping. */
|
|
1344
|
+
private _buildPluginContext;
|
|
1345
|
+
/**
|
|
1346
|
+
* Reload a single HNSW index by name.
|
|
1347
|
+
* Discovers the vector table via ReembeddablePlugin capability.
|
|
1348
|
+
* KV is handled directly since it's core-owned.
|
|
1349
|
+
*
|
|
1350
|
+
* The `name` comes from `index_state` and equals the plugin's `mod.name`
|
|
1351
|
+
* (e.g. `code:backend`, `git`, `docs`). This matches the key used in
|
|
1352
|
+
* `getOrCreateSharedHnsw()` during initialization.
|
|
1353
|
+
*/
|
|
1354
|
+
private _reloadIndex;
|
|
1355
|
+
/** Guard: throw descriptive error if not initialized. */
|
|
1356
|
+
private _requireInit;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/** HNSW index key for KV collections (core-owned). */
|
|
1360
|
+
declare const HNSW: {
|
|
1361
|
+
readonly KV: "kv";
|
|
1362
|
+
};
|
|
1363
|
+
type HnswKey = typeof HNSW[keyof typeof HNSW];
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* BrainBank — Local Embedding Provider
|
|
1367
|
+
*
|
|
1368
|
+
* Uses @xenova/transformers with all-MiniLM-L6-v2 (384 dims, WASM).
|
|
1369
|
+
* Downloads ~23MB on first use, cached locally.
|
|
1370
|
+
* No external API calls — runs entirely in-process.
|
|
1371
|
+
*/
|
|
1372
|
+
|
|
1373
|
+
declare class LocalEmbedding implements EmbeddingProvider {
|
|
1374
|
+
readonly dims: number;
|
|
1375
|
+
private _pipeline;
|
|
1376
|
+
private _modelName;
|
|
1377
|
+
private _cacheDir;
|
|
1378
|
+
constructor(options?: {
|
|
1379
|
+
model?: string;
|
|
1380
|
+
cacheDir?: string;
|
|
1381
|
+
});
|
|
1382
|
+
private _pipelinePromise;
|
|
1383
|
+
/**
|
|
1384
|
+
* Lazy-load the transformer pipeline.
|
|
1385
|
+
* Singleton — created once and reused.
|
|
1386
|
+
* Promise-deduped to prevent concurrent downloads.
|
|
1387
|
+
*/
|
|
1388
|
+
private _getPipeline;
|
|
1389
|
+
/**
|
|
1390
|
+
* Embed a single text string.
|
|
1391
|
+
* Returns a normalized Float32Array of length 384.
|
|
1392
|
+
*/
|
|
1393
|
+
embed(text: string): Promise<Float32Array>;
|
|
1394
|
+
/**
|
|
1395
|
+
* Embed multiple texts using real batch processing.
|
|
1396
|
+
* Chunks into groups of BATCH_SIZE to balance throughput vs memory.
|
|
1397
|
+
*/
|
|
1398
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
1399
|
+
close(): Promise<void>;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* BrainBank — OpenAI Embedding Provider
|
|
1404
|
+
*
|
|
1405
|
+
* Uses OpenAI's embedding API via fetch (no SDK dependency).
|
|
1406
|
+
* Supports text-embedding-3-small, text-embedding-3-large, and ada-002.
|
|
1407
|
+
*
|
|
1408
|
+
* Usage:
|
|
1409
|
+
* const brain = new BrainBank({
|
|
1410
|
+
* embeddingProvider: new OpenAIEmbedding({ model: 'text-embedding-3-small' }),
|
|
1411
|
+
* });
|
|
1412
|
+
*/
|
|
1413
|
+
|
|
1414
|
+
interface OpenAIEmbeddingOptions {
|
|
1415
|
+
/** OpenAI API key. Falls back to OPENAI_API_KEY env var. */
|
|
1416
|
+
apiKey?: string;
|
|
1417
|
+
/** Model name. Default: 'text-embedding-3-small' */
|
|
1418
|
+
model?: string;
|
|
1419
|
+
/** Vector dimensions. If omitted, uses model default. text-embedding-3-* supports custom dims. */
|
|
1420
|
+
dims?: number;
|
|
1421
|
+
/** Base URL override (for Azure, proxies, etc.) */
|
|
1422
|
+
baseUrl?: string;
|
|
1423
|
+
/** Request timeout in ms. Default: 30000 */
|
|
1424
|
+
timeout?: number;
|
|
1425
|
+
}
|
|
1426
|
+
declare class OpenAIEmbedding implements EmbeddingProvider {
|
|
1427
|
+
readonly dims: number;
|
|
1428
|
+
private _apiKey;
|
|
1429
|
+
private _model;
|
|
1430
|
+
private _baseUrl;
|
|
1431
|
+
private _requestDims;
|
|
1432
|
+
private _timeout;
|
|
1433
|
+
constructor(options?: OpenAIEmbeddingOptions);
|
|
1434
|
+
embed(text: string): Promise<Float32Array>;
|
|
1435
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
1436
|
+
close(): Promise<void>;
|
|
1437
|
+
private _isTokenLimitError;
|
|
1438
|
+
private _request;
|
|
1439
|
+
/** Handle API errors with token-limit retry logic. */
|
|
1440
|
+
private _handleApiError;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/**
|
|
1444
|
+
* BrainBank — Perplexity Standard Embedding Provider
|
|
1445
|
+
*
|
|
1446
|
+
* Uses Perplexity's embedding API via fetch (no SDK dependency).
|
|
1447
|
+
* Models: pplx-embed-v1-0.6b (1024d) and pplx-embed-v1-4b (2560d).
|
|
1448
|
+
*
|
|
1449
|
+
* Perplexity returns base64-encoded signed int8 vectors by default.
|
|
1450
|
+
* This provider decodes them to Float32Array for HNSW compatibility.
|
|
1451
|
+
*
|
|
1452
|
+
* Usage:
|
|
1453
|
+
* const brain = new BrainBank({
|
|
1454
|
+
* embeddingProvider: new PerplexityEmbedding({ model: 'pplx-embed-v1-4b' }),
|
|
1455
|
+
* });
|
|
1456
|
+
*/
|
|
1457
|
+
|
|
1458
|
+
interface PerplexityEmbeddingOptions {
|
|
1459
|
+
/** Perplexity API key. Falls back to PERPLEXITY_API_KEY env var. */
|
|
1460
|
+
apiKey?: string;
|
|
1461
|
+
/** Model name. Default: 'pplx-embed-v1-4b' */
|
|
1462
|
+
model?: string;
|
|
1463
|
+
/** Vector dimensions (Matryoshka reduction). If omitted, uses model default. */
|
|
1464
|
+
dims?: number;
|
|
1465
|
+
/** Base URL override. */
|
|
1466
|
+
baseUrl?: string;
|
|
1467
|
+
/** Request timeout in ms. Default: 30000 */
|
|
1468
|
+
timeout?: number;
|
|
1469
|
+
}
|
|
1470
|
+
declare class PerplexityEmbedding implements EmbeddingProvider {
|
|
1471
|
+
readonly dims: number;
|
|
1472
|
+
private _apiKey;
|
|
1473
|
+
private _model;
|
|
1474
|
+
private _baseUrl;
|
|
1475
|
+
private _requestDims;
|
|
1476
|
+
private _timeout;
|
|
1477
|
+
constructor(options?: PerplexityEmbeddingOptions);
|
|
1478
|
+
embed(text: string): Promise<Float32Array>;
|
|
1479
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
1480
|
+
close(): Promise<void>;
|
|
1481
|
+
private _request;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* BrainBank — Perplexity Contextualized Embedding Provider
|
|
1486
|
+
*
|
|
1487
|
+
* Uses Perplexity's contextualized embeddings API for document-aware vectors.
|
|
1488
|
+
* Chunks from the same document share context, improving retrieval quality.
|
|
1489
|
+
*
|
|
1490
|
+
* Models: pplx-embed-context-v1-0.6b (1024d), pplx-embed-context-v1-4b (2560d).
|
|
1491
|
+
*
|
|
1492
|
+
* Key difference from standard: input is string[][] (docs × chunks) and the
|
|
1493
|
+
* response has a nested structure. This provider adapts the flat BrainBank
|
|
1494
|
+
* EmbeddingProvider interface to the nested Perplexity API:
|
|
1495
|
+
* - embed(text) → wraps as [[text]]
|
|
1496
|
+
* - embedBatch(texts) → wraps as [texts] (one "document" of related chunks)
|
|
1497
|
+
*
|
|
1498
|
+
* Usage:
|
|
1499
|
+
* const brain = new BrainBank({
|
|
1500
|
+
* embeddingProvider: new PerplexityContextEmbedding(),
|
|
1501
|
+
* });
|
|
1502
|
+
*/
|
|
1503
|
+
|
|
1504
|
+
interface PerplexityContextEmbeddingOptions {
|
|
1505
|
+
/** Perplexity API key. Falls back to PERPLEXITY_API_KEY env var. */
|
|
1506
|
+
apiKey?: string;
|
|
1507
|
+
/** Model name. Default: 'pplx-embed-context-v1-4b' */
|
|
1508
|
+
model?: string;
|
|
1509
|
+
/** Vector dimensions (Matryoshka reduction). If omitted, uses model default. */
|
|
1510
|
+
dims?: number;
|
|
1511
|
+
/** Base URL override. */
|
|
1512
|
+
baseUrl?: string;
|
|
1513
|
+
/** Request timeout in ms. Default: 30000 */
|
|
1514
|
+
timeout?: number;
|
|
1515
|
+
}
|
|
1516
|
+
declare class PerplexityContextEmbedding implements EmbeddingProvider {
|
|
1517
|
+
readonly dims: number;
|
|
1518
|
+
private _apiKey;
|
|
1519
|
+
private _model;
|
|
1520
|
+
private _baseUrl;
|
|
1521
|
+
private _requestDims;
|
|
1522
|
+
private _timeout;
|
|
1523
|
+
constructor(options?: PerplexityContextEmbeddingOptions);
|
|
1524
|
+
/** Embed a single text. Wraps as [[text]] for the contextualized API. */
|
|
1525
|
+
embed(text: string): Promise<Float32Array>;
|
|
1526
|
+
/**
|
|
1527
|
+
* Embed multiple texts as chunks of contextualized documents.
|
|
1528
|
+
* Splits into sub-documents to stay under Perplexity's 32k token/doc limit.
|
|
1529
|
+
*/
|
|
1530
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
1531
|
+
close(): Promise<void>;
|
|
1532
|
+
/** Send a contextualized request. Input is string[][] (docs × chunks). */
|
|
1533
|
+
private _request;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* BrainBank — Embedding Worker Proxy
|
|
1538
|
+
*
|
|
1539
|
+
* Drop-in replacement for any EmbeddingProvider that offloads
|
|
1540
|
+
* embedding computation to a dedicated worker thread.
|
|
1541
|
+
* The main thread's event loop stays free for serving searches.
|
|
1542
|
+
*
|
|
1543
|
+
* Usage:
|
|
1544
|
+
* const proxy = new EmbeddingWorkerProxy('local', { model: '...' });
|
|
1545
|
+
* await proxy.ready();
|
|
1546
|
+
* const vec = await proxy.embed('hello');
|
|
1547
|
+
*/
|
|
1548
|
+
|
|
1549
|
+
declare class EmbeddingWorkerProxy implements EmbeddingProvider {
|
|
1550
|
+
private _worker;
|
|
1551
|
+
private _nextId;
|
|
1552
|
+
private _pending;
|
|
1553
|
+
private _ready;
|
|
1554
|
+
private _dims;
|
|
1555
|
+
/** Embedding dimensions (available after `ready()` resolves). */
|
|
1556
|
+
get dims(): number;
|
|
1557
|
+
constructor(providerType: string, providerOptions?: Record<string, unknown>);
|
|
1558
|
+
/** Wait for the worker to be ready (provider loaded). */
|
|
1559
|
+
ready(): Promise<void>;
|
|
1560
|
+
/** Send a request to the worker and wait for the response. */
|
|
1561
|
+
private _send;
|
|
1562
|
+
/** Embed a single text string. */
|
|
1563
|
+
embed(text: string): Promise<Float32Array>;
|
|
1564
|
+
/** Embed multiple texts in a batch. */
|
|
1565
|
+
embedBatch(texts: string[]): Promise<Float32Array[]>;
|
|
1566
|
+
/** Terminate the worker. */
|
|
1567
|
+
close(): Promise<void>;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* HttpServer — Lightweight JSON API for BrainBank.
|
|
1572
|
+
*
|
|
1573
|
+
* Exposes BrainBank operations over HTTP so CLI commands can delegate
|
|
1574
|
+
* to a running server instead of cold-loading models each time.
|
|
1575
|
+
*
|
|
1576
|
+
* Routes:
|
|
1577
|
+
* POST /context → brain.getContext()
|
|
1578
|
+
* POST /search → brain.search()
|
|
1579
|
+
* POST /hsearch → brain.hybridSearch()
|
|
1580
|
+
* POST /ksearch → brain.searchBM25()
|
|
1581
|
+
* POST /index → brain.index()
|
|
1582
|
+
* GET /health → { ok, pid, uptime, port }
|
|
1583
|
+
*/
|
|
1584
|
+
|
|
1585
|
+
interface HttpServerOptions {
|
|
1586
|
+
port?: number;
|
|
1587
|
+
/** Factory to create a BrainBank instance for a given repo path. */
|
|
1588
|
+
factory: (repoPath: string) => Promise<BrainBank>;
|
|
1589
|
+
/** Default repo path when request doesn't specify one. */
|
|
1590
|
+
defaultRepo?: string;
|
|
1591
|
+
/** Called when an error occurs during pool operations. */
|
|
1592
|
+
onError?: (repo: string, err: unknown) => void;
|
|
1593
|
+
/** Called on server lifecycle events. */
|
|
1594
|
+
onLog?: (msg: string) => void;
|
|
1595
|
+
}
|
|
1596
|
+
declare class HttpServer {
|
|
1597
|
+
private _server;
|
|
1598
|
+
private _pool;
|
|
1599
|
+
private _port;
|
|
1600
|
+
private _defaultRepo;
|
|
1601
|
+
private _startTime;
|
|
1602
|
+
private _log;
|
|
1603
|
+
constructor(options: HttpServerOptions);
|
|
1604
|
+
/** Start listening. Writes PID file for daemon detection. */
|
|
1605
|
+
start(): Promise<void>;
|
|
1606
|
+
/** Stop the server and clean up. */
|
|
1607
|
+
close(): void;
|
|
1608
|
+
get port(): number;
|
|
1609
|
+
private _handleRequest;
|
|
1610
|
+
private _handleHealth;
|
|
1611
|
+
private _handleContext;
|
|
1612
|
+
private _handleIndex;
|
|
1613
|
+
private _handleSearch;
|
|
1614
|
+
private _readBody;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Daemon — PID file management for the BrainBank HTTP server.
|
|
1619
|
+
*
|
|
1620
|
+
* PID file: ~/.cache/brainbank/server.pid
|
|
1621
|
+
* Format: JSON { pid: number, port: number }
|
|
1622
|
+
*
|
|
1623
|
+
* Used by:
|
|
1624
|
+
* - `brainbank daemon` to write PID on start
|
|
1625
|
+
* - `brainbank daemon stop` to find and kill the daemon
|
|
1626
|
+
* - `brainbank status` to report server state
|
|
1627
|
+
* - CLI commands to detect running server for delegation
|
|
1628
|
+
*/
|
|
1629
|
+
declare const DEFAULT_PORT = 8181;
|
|
1630
|
+
interface PidInfo {
|
|
1631
|
+
pid: number;
|
|
1632
|
+
port: number;
|
|
1633
|
+
}
|
|
1634
|
+
/** Write the PID file after server starts. */
|
|
1635
|
+
declare function writePid(pid: number, port: number): void;
|
|
1636
|
+
/** Read PID info from file. Returns null if missing or malformed. */
|
|
1637
|
+
declare function readPid(): PidInfo | null;
|
|
1638
|
+
/** Remove the PID file. */
|
|
1639
|
+
declare function removePid(): void;
|
|
1640
|
+
/**
|
|
1641
|
+
* Check if the server process is still alive.
|
|
1642
|
+
* Uses `kill(pid, 0)` which doesn't actually send a signal —
|
|
1643
|
+
* it just checks whether the process exists.
|
|
1644
|
+
*/
|
|
1645
|
+
declare function isServerRunning(): PidInfo | null;
|
|
1646
|
+
/** Get the server URL if running, or null. */
|
|
1647
|
+
declare function getServerUrl(): string | null;
|
|
1648
|
+
|
|
1649
|
+
/**
|
|
1650
|
+
* BrainBank — Haiku Pruner
|
|
1651
|
+
*
|
|
1652
|
+
* LLM-based noise filter using Anthropic's Haiku 4.5 model.
|
|
1653
|
+
* Binary classification: for each search result, Haiku decides
|
|
1654
|
+
* "relevant" or "noise" based on filePath, metadata, and full
|
|
1655
|
+
* file content (capped at ~8K chars per item by prune.ts).
|
|
1656
|
+
*
|
|
1657
|
+
* Latency: ~300-600ms.
|
|
1658
|
+
*/
|
|
1659
|
+
|
|
1660
|
+
interface HaikuPrunerOptions {
|
|
1661
|
+
/** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */
|
|
1662
|
+
apiKey?: string;
|
|
1663
|
+
/** Model to use. Default: claude-haiku-4-5-20251001 */
|
|
1664
|
+
model?: string;
|
|
1665
|
+
}
|
|
1666
|
+
declare class HaikuPruner implements Pruner {
|
|
1667
|
+
private readonly _apiKey;
|
|
1668
|
+
private readonly _model;
|
|
1669
|
+
constructor(options?: HaikuPrunerOptions);
|
|
1670
|
+
prune(query: string, items: PrunerItem[]): Promise<number[]>;
|
|
1671
|
+
close(): Promise<void>;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/**
|
|
1675
|
+
* BrainBank — Prune Utility
|
|
1676
|
+
*
|
|
1677
|
+
* Bridges SearchResult[] → Pruner.prune() → filtered SearchResult[].
|
|
1678
|
+
* Converts results to lightweight PrunerItems with full content previews,
|
|
1679
|
+
* calls the pruner, and filters out dropped results.
|
|
1680
|
+
*
|
|
1681
|
+
* Content strategy: send the FULL chunk content to the pruner so it can
|
|
1682
|
+
* make informed decisions. A per-item character cap prevents cost blowups
|
|
1683
|
+
* on monster files — when truncated, the middle is kept (imports + core
|
|
1684
|
+
* logic) rather than just the top.
|
|
1685
|
+
*/
|
|
1686
|
+
|
|
1687
|
+
/** Run the pruner on search results. Returns results in the order the pruner chose. */
|
|
1688
|
+
declare function pruneResults(query: string, results: SearchResult[], pruner: Pruner): Promise<SearchResult[]>;
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* BrainBank — Haiku Expander
|
|
1692
|
+
*
|
|
1693
|
+
* LLM-powered context expansion using Anthropic's Haiku 4.5 model.
|
|
1694
|
+
* After search + pruning, reviews a manifest of available chunks
|
|
1695
|
+
* and requests additional IDs to include.
|
|
1696
|
+
*
|
|
1697
|
+
* Flow:
|
|
1698
|
+
* 1. Receives lightweight manifest (~20 chars per chunk)
|
|
1699
|
+
* 2. Haiku selects additional chunk IDs (just numbers, fast)
|
|
1700
|
+
* 3. Caller fetches those chunks from DB and splices into results
|
|
1701
|
+
*
|
|
1702
|
+
* Designed for minimal token usage:
|
|
1703
|
+
* - Input: ~2,000-3,000 tokens (manifest)
|
|
1704
|
+
* - Output: ~50-100 tokens (ID array)
|
|
1705
|
+
* - Cost: ~$0.001 per call
|
|
1706
|
+
* - Latency: ~300-600ms
|
|
1707
|
+
*
|
|
1708
|
+
* Fail-open: any error returns empty array (no expansion).
|
|
1709
|
+
*/
|
|
1710
|
+
|
|
1711
|
+
interface HaikuExpanderOptions {
|
|
1712
|
+
/** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */
|
|
1713
|
+
apiKey?: string;
|
|
1714
|
+
/** Model to use. Default: claude-haiku-4-5-20251001 */
|
|
1715
|
+
model?: string;
|
|
1716
|
+
}
|
|
1717
|
+
declare class HaikuExpander implements Expander {
|
|
1718
|
+
private readonly _apiKey;
|
|
1719
|
+
private readonly _model;
|
|
1720
|
+
constructor(options?: HaikuExpanderOptions);
|
|
1721
|
+
expand(query: string, currentIds: number[], manifest: ExpanderManifestItem[]): Promise<ExpanderResult>;
|
|
1722
|
+
/** Parse Haiku response — handles both `{ ids, note }` and bare `[...]` formats. */
|
|
1723
|
+
private _parseResponse;
|
|
1724
|
+
close(): Promise<void>;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* BrainBank — Provider Key
|
|
1729
|
+
*
|
|
1730
|
+
* Infers a stable key from an existing EmbeddingProvider instance.
|
|
1731
|
+
* Lives in lib/ (Layer 0) to avoid db/ → providers/ dependency.
|
|
1732
|
+
*/
|
|
1733
|
+
|
|
1734
|
+
/** Known embedding provider keys. */
|
|
1735
|
+
type EmbeddingKey = 'local' | 'openai' | 'perplexity' | 'perplexity-context';
|
|
1736
|
+
/** Infer a stable key from an existing provider instance. */
|
|
1737
|
+
declare function providerKey(p: EmbeddingProvider): EmbeddingKey;
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* BrainBank — Embedding Provider Resolver
|
|
1741
|
+
*
|
|
1742
|
+
* Resolves an EmbeddingProvider from a stored key string.
|
|
1743
|
+
* Used by the Initializer to auto-resolve from DB config.
|
|
1744
|
+
*/
|
|
1745
|
+
|
|
1746
|
+
/** Resolve an EmbeddingProvider from a key string. Lazy-loads the provider module. */
|
|
1747
|
+
declare function resolveEmbedding(key: string): Promise<EmbeddingProvider>;
|
|
1748
|
+
|
|
1749
|
+
declare const DEFAULTS: ResolvedConfig;
|
|
1750
|
+
/**
|
|
1751
|
+
* Merge partial config with defaults.
|
|
1752
|
+
* All fields become required.
|
|
1753
|
+
* Relative dbPath is resolved against repoPath.
|
|
1754
|
+
*/
|
|
1755
|
+
declare function resolveConfig(partial?: BrainBankConfig): ResolvedConfig;
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* BrainBank — Maximum Marginal Relevance (MMR)
|
|
1759
|
+
*
|
|
1760
|
+
* Diversifies vector search results to avoid returning redundant items.
|
|
1761
|
+
* λ=1.0 → pure relevance, λ=0.0 → pure diversity.
|
|
1762
|
+
* Default λ=0.7 balances both.
|
|
1763
|
+
*/
|
|
1764
|
+
|
|
1765
|
+
/**
|
|
1766
|
+
* Search with Maximum Marginal Relevance for diversified results.
|
|
1767
|
+
*
|
|
1768
|
+
* Algorithm:
|
|
1769
|
+
* 1. Get 3x candidates from HNSW
|
|
1770
|
+
* 2. Greedily select items that maximize: λ * relevance - (1-λ) * max_sim_to_selected
|
|
1771
|
+
*/
|
|
1772
|
+
declare function searchMMR(index: VectorIndex, query: Float32Array, vectorCache: Map<number, Float32Array>, k: number, lambda?: number): SearchHit[];
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* BrainBank — Language Registry
|
|
1776
|
+
*
|
|
1777
|
+
* Supported file extensions, language mappings, and ignore lists.
|
|
1778
|
+
* Controls which files get indexed and how they're chunked.
|
|
1779
|
+
*/
|
|
1780
|
+
declare const SUPPORTED_EXTENSIONS: Record<string, string>;
|
|
1781
|
+
declare const IGNORE_DIRS: Set<string>;
|
|
1782
|
+
/** Check if a file extension is supported for indexing. */
|
|
1783
|
+
declare function isSupported(filePath: string): boolean;
|
|
1784
|
+
/** Get the language name for a file. Returns undefined if not supported. */
|
|
1785
|
+
declare function getLanguage(filePath: string): string | undefined;
|
|
1786
|
+
/** Check if a directory name should be ignored. */
|
|
1787
|
+
declare function isIgnoredDir(dirName: string): boolean;
|
|
1788
|
+
/** Check if a filename should be ignored. */
|
|
1789
|
+
declare function isIgnoredFile(fileName: string): boolean;
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* BrainBank — Math Utilities
|
|
1793
|
+
*
|
|
1794
|
+
* Pure vector math functions for similarity calculations.
|
|
1795
|
+
* No dependencies — works on Float32Array directly.
|
|
1796
|
+
*/
|
|
1797
|
+
/**
|
|
1798
|
+
* Cosine similarity between two vectors.
|
|
1799
|
+
* Assumes vectors are already normalized (unit length).
|
|
1800
|
+
* Returns value between -1.0 and 1.0.
|
|
1801
|
+
*/
|
|
1802
|
+
declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
|
|
1803
|
+
/**
|
|
1804
|
+
* L2-normalize a vector to unit length.
|
|
1805
|
+
* Returns a new Float32Array.
|
|
1806
|
+
*/
|
|
1807
|
+
declare function normalize(vec: Float32Array): Float32Array;
|
|
1808
|
+
/**
|
|
1809
|
+
* Convert a Float32Array to a Buffer for SQLite storage.
|
|
1810
|
+
* Handles views with non-zero byteOffset (e.g. from batched embedding output).
|
|
1811
|
+
* Using Buffer.from(vec.buffer) directly is WRONG for views — it copies the entire parent buffer.
|
|
1812
|
+
*/
|
|
1813
|
+
declare function vecToBuffer(vec: Float32Array): Buffer;
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* BrainBank — KV Service
|
|
1817
|
+
*
|
|
1818
|
+
* Owns the shared HNSW index and vector cache for KV collections.
|
|
1819
|
+
* Provides collection creation, listing, and deletion.
|
|
1820
|
+
* Extracted from BrainBank to separate infrastructure from facade.
|
|
1821
|
+
*/
|
|
1822
|
+
|
|
1823
|
+
declare class KVService {
|
|
1824
|
+
private _db;
|
|
1825
|
+
private _embedding;
|
|
1826
|
+
private _hnsw;
|
|
1827
|
+
private _vecs;
|
|
1828
|
+
private _collections;
|
|
1829
|
+
constructor(_db: DatabaseAdapter, _embedding: EmbeddingProvider, _hnsw: HNSWIndex, _vecs: Map<number, Float32Array>);
|
|
1830
|
+
/** Get or create a named collection. */
|
|
1831
|
+
collection(name: string): Collection;
|
|
1832
|
+
/** List all collection names that have data. */
|
|
1833
|
+
listNames(): string[];
|
|
1834
|
+
/** Delete a collection's data and evict from cache. Removes vectors from HNSW to prevent ghost entries. */
|
|
1835
|
+
delete(name: string): void;
|
|
1836
|
+
/** Access the shared HNSW index (used by reembed). */
|
|
1837
|
+
get hnsw(): HNSWIndex;
|
|
1838
|
+
/** Access the shared vector cache. @internal */
|
|
1839
|
+
get vecs(): Map<number, Float32Array>;
|
|
1840
|
+
/** Clear all cached collections and vectors. */
|
|
1841
|
+
clear(): void;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/**
|
|
1845
|
+
* BrainBank — Plugin Registry
|
|
1846
|
+
*
|
|
1847
|
+
* Manages registration and lookup of plugins.
|
|
1848
|
+
* Extracted from BrainBank so the facade stays focused on orchestration.
|
|
1849
|
+
*
|
|
1850
|
+
* Responsibilities:
|
|
1851
|
+
* - Store plugins by name
|
|
1852
|
+
* - Alias resolution (currently none; add here if needed)
|
|
1853
|
+
* - Consistent error messages on missing plugins
|
|
1854
|
+
*/
|
|
1855
|
+
|
|
1856
|
+
declare class PluginRegistry {
|
|
1857
|
+
private _map;
|
|
1858
|
+
/** Store a plugin. Duplicate names silently overwrite. */
|
|
1859
|
+
register(plugin: Plugin): void;
|
|
1860
|
+
/** Check whether a plugin is registered (exact match). */
|
|
1861
|
+
has(name: string): boolean;
|
|
1862
|
+
/**
|
|
1863
|
+
* Get a plugin by name. Throws a descriptive error if not found.
|
|
1864
|
+
*
|
|
1865
|
+
* Resolution order:
|
|
1866
|
+
* 1. Alias map (currently empty)
|
|
1867
|
+
* 2. Exact match
|
|
1868
|
+
*/
|
|
1869
|
+
get<T extends Plugin = Plugin>(name: string): T;
|
|
1870
|
+
/** All registered plugin names (insertion order). */
|
|
1871
|
+
get names(): string[];
|
|
1872
|
+
/** All registered plugin instances (insertion order). */
|
|
1873
|
+
get all(): Plugin[];
|
|
1874
|
+
/**
|
|
1875
|
+
* Underlying Map.
|
|
1876
|
+
* Prefer `all` everywhere else.
|
|
1877
|
+
*/
|
|
1878
|
+
get raw(): Map<string, Plugin>;
|
|
1879
|
+
/** Remove all registered plugins. Called by BrainBank.close(). */
|
|
1880
|
+
clear(): void;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
/**
|
|
1884
|
+
* BrainBank — Context Builder
|
|
1885
|
+
*
|
|
1886
|
+
* Orchestrates the context-building pipeline:
|
|
1887
|
+
* 1. Vector search (primary)
|
|
1888
|
+
* 2. Path scoping (filter)
|
|
1889
|
+
* 3. LLM noise pruning (optional)
|
|
1890
|
+
* 4. Session dedup (filter)
|
|
1891
|
+
* 5. LLM context expansion (optional — expander field)
|
|
1892
|
+
* 6. Plugin formatters (output)
|
|
1893
|
+
*
|
|
1894
|
+
* All search post-processing lives in `bm25-boost.ts`.
|
|
1895
|
+
* Plugin-agnostic — discovers formatters from ContextFormatterPlugin.
|
|
1896
|
+
*/
|
|
1897
|
+
|
|
1898
|
+
declare class ContextBuilder {
|
|
1899
|
+
private _search;
|
|
1900
|
+
private _registry;
|
|
1901
|
+
private _pruner?;
|
|
1902
|
+
private _embedding?;
|
|
1903
|
+
private _configFields;
|
|
1904
|
+
private _expander?;
|
|
1905
|
+
constructor(_search: SearchStrategy | undefined, _registry: PluginRegistry, _pruner?: Pruner | undefined, _embedding?: EmbeddingProvider | undefined, _configFields?: Record<string, unknown>, _expander?: Expander | undefined);
|
|
1906
|
+
/** Set config-level context field defaults (from config.json "context" section). */
|
|
1907
|
+
set configFields(fields: Record<string, unknown>);
|
|
1908
|
+
/** Set the expander instance. */
|
|
1909
|
+
set expander(expander: Expander | undefined);
|
|
1910
|
+
/** Build a full context block for a task. Returns markdown for system prompt. */
|
|
1911
|
+
build(task: string, options?: ContextOptions): Promise<string>;
|
|
1912
|
+
/** Invoke ContextFormatterPlugins. */
|
|
1913
|
+
private _appendFormatterResults;
|
|
1914
|
+
/**
|
|
1915
|
+
* Resolve context fields: plugin defaults ← config.json ← per-query.
|
|
1916
|
+
* Returns a flat Record with the final value for each field.
|
|
1917
|
+
*/
|
|
1918
|
+
private _resolveFields;
|
|
1919
|
+
/**
|
|
1920
|
+
* Run LLM expansion: build manifest of candidate chunks from files
|
|
1921
|
+
* NOT already in search results, call expander, resolve selected IDs.
|
|
1922
|
+
*/
|
|
1923
|
+
private _expand;
|
|
1924
|
+
/** Collect results from SearchablePlugins that don't have their own formatter. */
|
|
1925
|
+
private _appendSearchableResults;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* BrainBank — Composite Vector Search
|
|
1930
|
+
*
|
|
1931
|
+
* Generic orchestrator for domain-specific vector searches.
|
|
1932
|
+
* Embeds the query once, delegates to registered DomainVectorSearch strategies.
|
|
1933
|
+
* Uses round-robin interleaving when multiple strategies exist to ensure
|
|
1934
|
+
* balanced representation across domains.
|
|
1935
|
+
* Plugin-agnostic — strategies are discovered at wiring time.
|
|
1936
|
+
*/
|
|
1937
|
+
|
|
1938
|
+
interface CompositeVectorConfig {
|
|
1939
|
+
strategies: Map<string, DomainVectorSearch>;
|
|
1940
|
+
embedding: EmbeddingProvider;
|
|
1941
|
+
/** Default K values per strategy name. Strategies not listed default to 0. */
|
|
1942
|
+
defaults?: Record<string, number>;
|
|
1943
|
+
}
|
|
1944
|
+
declare class CompositeVectorSearch implements SearchStrategy {
|
|
1945
|
+
private _c;
|
|
1946
|
+
/** Default K when no source override is provided. */
|
|
1947
|
+
private static readonly DEFAULT_K;
|
|
1948
|
+
constructor(_c: CompositeVectorConfig);
|
|
1949
|
+
/** Search across all registered domain strategies with score-based merge. */
|
|
1950
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* BrainBank — Composite BM25 Search Strategy
|
|
1955
|
+
*
|
|
1956
|
+
* Generic BM25 coordinator that discovers BM25SearchPlugin instances
|
|
1957
|
+
* from the registry and delegates per-source keyword search.
|
|
1958
|
+
*/
|
|
1959
|
+
|
|
1960
|
+
declare class CompositeBM25Search implements SearchStrategy {
|
|
1961
|
+
private _registry;
|
|
1962
|
+
constructor(_registry: PluginRegistry);
|
|
1963
|
+
/**
|
|
1964
|
+
* Run BM25 keyword search across all plugins that implement BM25SearchPlugin.
|
|
1965
|
+
* Each plugin searches its own FTS5 tables.
|
|
1966
|
+
*/
|
|
1967
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
1968
|
+
/** Rebuild FTS5 indices across all BM25 plugins. */
|
|
1969
|
+
rebuild(): void;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/**
|
|
1973
|
+
* BrainBank — Reciprocal Rank Fusion (RRF)
|
|
1974
|
+
*
|
|
1975
|
+
* Combines results from multiple search systems (vector + BM25)
|
|
1976
|
+
* using the RRF algorithm: score = Σ 1/(k + rank_i)
|
|
1977
|
+
*
|
|
1978
|
+
* This is the same algorithm used by Elasticsearch, QMD, and most
|
|
1979
|
+
* production hybrid search systems. Simple but very effective.
|
|
1980
|
+
*
|
|
1981
|
+
* Reference: Cormack et al., "Reciprocal Rank Fusion outperforms
|
|
1982
|
+
* Condorcet and individual Rank Learning Methods" (2009)
|
|
1983
|
+
*/
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Fuse ranked lists from different search systems into a single ranked list.
|
|
1987
|
+
*
|
|
1988
|
+
* @param resultSets - Arrays of SearchResult from different systems (e.g. vector, BM25)
|
|
1989
|
+
* @param k - Smoothing constant. Default: 60 (standard value). Higher = less emphasis on top ranks.
|
|
1990
|
+
* @param maxResults - Maximum results to return.
|
|
1991
|
+
*/
|
|
1992
|
+
declare function reciprocalRankFusion(resultSets: SearchResult[][], k?: number, maxResults?: number): SearchResult[];
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* BrainBank — FTS Utilities
|
|
1996
|
+
*
|
|
1997
|
+
* Shared helpers for SQLite FTS5 query sanitization.
|
|
1998
|
+
*/
|
|
1999
|
+
/**
|
|
2000
|
+
* Sanitize a user query for FTS5 syntax.
|
|
2001
|
+
* Strips operators that would cause parse errors, splits compound words,
|
|
2002
|
+
* and converts words to implicit AND with exact-match quoting.
|
|
2003
|
+
*/
|
|
2004
|
+
declare function sanitizeFTS(query: string): string;
|
|
2005
|
+
/**
|
|
2006
|
+
* Normalize BM25 score from SQLite (negative, lower = better)
|
|
2007
|
+
* to 0.0–1.0 (higher = better) for consistency with vector search.
|
|
2008
|
+
*/
|
|
2009
|
+
declare function normalizeBM25(rawScore: number): number;
|
|
2010
|
+
/**
|
|
2011
|
+
* Escape SQL LIKE wildcard characters (`%`, `_`, `\`).
|
|
2012
|
+
* Use with `LIKE ? ESCAPE '\'` in queries.
|
|
2013
|
+
*/
|
|
2014
|
+
declare function escapeLike(s: string): string;
|
|
2015
|
+
|
|
2016
|
+
/**
|
|
2017
|
+
* BrainBank — Database Metadata
|
|
2018
|
+
*
|
|
2019
|
+
* Helpers for reading/writing metadata stored in core SQLite tables:
|
|
2020
|
+
*
|
|
2021
|
+
* - **Index State** — cross-process HNSW version tracking.
|
|
2022
|
+
* Processes compare in-memory versions with DB to detect staleness
|
|
2023
|
+
* and trigger hot-reload via `ensureFresh()`.
|
|
2024
|
+
*
|
|
2025
|
+
* - **Embedding Meta** — tracks which embedding provider is stored in
|
|
2026
|
+
* the database. Detects dimension mismatches at startup and updates
|
|
2027
|
+
* metadata after `reembed()`.
|
|
2028
|
+
*/
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* Increment the version for a given index name.
|
|
2032
|
+
* Sets writer_pid to current process PID.
|
|
2033
|
+
* Uses UPSERT so the row is created on first call.
|
|
2034
|
+
*/
|
|
2035
|
+
declare function bumpVersion(db: DatabaseAdapter, name: string): number;
|
|
2036
|
+
/**
|
|
2037
|
+
* Get all index versions as a Map.
|
|
2038
|
+
* Used by `ensureFresh()` to compare against in-memory versions.
|
|
2039
|
+
*/
|
|
2040
|
+
declare function getVersions(db: DatabaseAdapter): Map<string, number>;
|
|
2041
|
+
/** Get the version of a single index. Returns 0 if not found. */
|
|
2042
|
+
declare function getVersion(db: DatabaseAdapter, name: string): number;
|
|
2043
|
+
|
|
2044
|
+
/**
|
|
2045
|
+
* BrainBank — Write Lock
|
|
2046
|
+
*
|
|
2047
|
+
* Advisory file lock for cross-process HNSW write exclusion.
|
|
2048
|
+
* Uses `O_CREAT | O_EXCL` for atomic lock creation — works on all OS.
|
|
2049
|
+
* Stale locks (dead PID) are detected and stolen automatically.
|
|
2050
|
+
*/
|
|
2051
|
+
/**
|
|
2052
|
+
* Acquire an advisory lock. Blocks with exponential backoff if another
|
|
2053
|
+
* process holds the lock. Steals stale locks from dead processes.
|
|
2054
|
+
*
|
|
2055
|
+
* @throws After MAX_WAIT_MS if the lock cannot be acquired.
|
|
2056
|
+
*/
|
|
2057
|
+
declare function acquireLock(lockDir: string, name: string): Promise<void>;
|
|
2058
|
+
/** Release an advisory lock. Safe to call even if not held. */
|
|
2059
|
+
declare function releaseLock(lockDir: string, name: string): void;
|
|
2060
|
+
/**
|
|
2061
|
+
* Execute a function while holding an advisory lock.
|
|
2062
|
+
* Lock is always released, even on error.
|
|
2063
|
+
*/
|
|
2064
|
+
declare function withLock<T>(lockDir: string, name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
2065
|
+
|
|
2066
|
+
/**
|
|
2067
|
+
* BrainBank — SQLite Adapter
|
|
2068
|
+
*
|
|
2069
|
+
* Implements `DatabaseAdapter` using Node.js built-in `node:sqlite`.
|
|
2070
|
+
* Zero native addons — no ABI issues across Node versions.
|
|
2071
|
+
* Handles WAL mode, directory creation, schema init, and transactions.
|
|
2072
|
+
*/
|
|
2073
|
+
|
|
2074
|
+
declare class SQLiteAdapter implements DatabaseAdapter {
|
|
2075
|
+
private _db;
|
|
2076
|
+
readonly capabilities: AdapterCapabilities;
|
|
2077
|
+
constructor(dbPath: string);
|
|
2078
|
+
/** Prepare a reusable statement. */
|
|
2079
|
+
prepare<T = unknown>(sql: string): PreparedStatement<T>;
|
|
2080
|
+
/** Execute raw SQL (no results). */
|
|
2081
|
+
exec(sql: string): void;
|
|
2082
|
+
/** Run a function inside a transaction. Auto-commits on success, auto-rollbacks on error. */
|
|
2083
|
+
transaction<T>(fn: () => T): T;
|
|
2084
|
+
/** Run a prepared statement on multiple rows. Wraps in a single transaction. */
|
|
2085
|
+
batch<T extends unknown[]>(sql: string, rows: T[]): void;
|
|
2086
|
+
/** Close the database. */
|
|
2087
|
+
close(): void;
|
|
2088
|
+
/**
|
|
2089
|
+
* Access the underlying `node:sqlite` DatabaseSync instance.
|
|
2090
|
+
*
|
|
2091
|
+
* @deprecated Use `DatabaseAdapter` methods instead. This exists
|
|
2092
|
+
* only for gradual migration of plugins that depend on driver internals.
|
|
2093
|
+
*/
|
|
2094
|
+
raw<T = unknown>(): T;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/**
|
|
2098
|
+
* BrainBank — Brain Context
|
|
2099
|
+
*
|
|
2100
|
+
* Portable input for `createBrain()`. Decouples the factory from
|
|
2101
|
+
* `process.argv` / `process.env` so it can be called from the CLI,
|
|
2102
|
+
* MCP server, tests, or any programmatic consumer.
|
|
2103
|
+
*/
|
|
2104
|
+
/** Everything the factory needs to build a BrainBank instance. */
|
|
2105
|
+
interface BrainContext {
|
|
2106
|
+
/** Repository root path. */
|
|
2107
|
+
repoPath: string;
|
|
2108
|
+
/** Environment variable overrides. Falls back to `process.env`. */
|
|
2109
|
+
env?: Record<string, string | undefined>;
|
|
2110
|
+
/** CLI flag overrides (e.g. `{ ignore: 'dist,vendor' }`). */
|
|
2111
|
+
flags?: Record<string, string | undefined>;
|
|
2112
|
+
}
|
|
2113
|
+
/** Build a `BrainContext` from CLI argv + process.env. */
|
|
2114
|
+
declare function contextFromCLI(repoPath?: string): BrainContext;
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* BrainBank CLI — Config Loader
|
|
2118
|
+
*
|
|
2119
|
+
* Loads .brainbank/config.json (or .ts/.js/.mjs fallback).
|
|
2120
|
+
* Config priority: CLI flags > config file > defaults.
|
|
2121
|
+
*/
|
|
2122
|
+
|
|
2123
|
+
/** Full .brainbank/config.json schema. */
|
|
2124
|
+
interface ProjectConfig {
|
|
2125
|
+
plugins?: string[];
|
|
2126
|
+
embedding?: string;
|
|
2127
|
+
pruner?: string;
|
|
2128
|
+
maxFileSize?: number;
|
|
2129
|
+
indexers?: Plugin[];
|
|
2130
|
+
brainbank?: Partial<BrainBankConfig>;
|
|
2131
|
+
/** Optional API keys — override env vars. Kept out of version control. */
|
|
2132
|
+
keys?: {
|
|
2133
|
+
anthropic?: string;
|
|
2134
|
+
perplexity?: string;
|
|
2135
|
+
openai?: string;
|
|
2136
|
+
};
|
|
2137
|
+
/** Context field defaults (e.g. { lines: true, callTree: true, symbols: false }). */
|
|
2138
|
+
context?: Record<string, unknown>;
|
|
2139
|
+
/** Per-plugin config sections (e.g. code, git, docs). */
|
|
2140
|
+
[pluginName: string]: unknown;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* BrainBank CLI — Brain Factory
|
|
2145
|
+
*
|
|
2146
|
+
* Creates a configured BrainBank instance with dynamically loaded plugins,
|
|
2147
|
+
* auto-discovered indexers, and config file support.
|
|
2148
|
+
* Delegates to focused modules in factory/.
|
|
2149
|
+
*/
|
|
2150
|
+
|
|
2151
|
+
/** Reset factory caches. Useful for tests. */
|
|
2152
|
+
declare function resetFactoryCache(): void;
|
|
2153
|
+
/**
|
|
2154
|
+
* Create a BrainBank with built-in + discovered + config plugins.
|
|
2155
|
+
*
|
|
2156
|
+
* Accepts either a `BrainContext` (for programmatic use) or an optional
|
|
2157
|
+
* `repoPath` string (for CLI backward compat — builds context from argv).
|
|
2158
|
+
*/
|
|
2159
|
+
declare function createBrain(contextOrRepo?: BrainContext | string): Promise<BrainBank>;
|
|
2160
|
+
|
|
2161
|
+
export { type AdapterCapabilities, type BM25SearchPlugin, BrainBank, type BrainBankConfig, type BrainContext, type CoEditPlugin, type CoEditSuggestion, type CodeChunk, type CodeResult, type CodeResultMetadata, Collection, type CollectionAddOptions, type CollectionItem, type CollectionResult, type CollectionSearchOptions, type CommitResult, type CommitResultMetadata, CompositeBM25Search, CompositeVectorSearch, ContextBuilder, type ContextFieldDef, type ContextFieldPlugin, type ContextFormatterPlugin, type ContextOptions, type CountRow, DEFAULTS, DEFAULT_PORT, type DatabaseAdapter, type DocChunk, type DocsPlugin, type DocumentCollection, type DocumentResult, type DocumentResultMetadata, type DomainVectorSearch, type EmbeddingKey, type EmbeddingMetaRow, type EmbeddingProvider, EmbeddingWorkerProxy, type ExecuteResult, type ExpandablePlugin, type Expander, type ExpanderManifestItem, type ExpanderResult, type FileResolvablePlugin, type GitCommitRecord, HNSW, HNSWIndex, HaikuExpander, type HaikuExpanderOptions, HaikuPruner, type HaikuPrunerOptions, type HnswKey, HttpServer, type HttpServerOptions, IGNORE_DIRS, type IncrementalTracker, type IndexResult, type IndexablePlugin, KVService, type KvDataRow, type KvVectorRow, LocalEmbedding, type MigratablePlugin, type Migration, OpenAIEmbedding, type OpenAIEmbeddingOptions, PerplexityContextEmbedding, type PerplexityContextEmbeddingOptions, PerplexityEmbedding, type PerplexityEmbeddingOptions, type Plugin, type PluginContext, type PluginPreviewLine, type PluginScanInfo, type PreparedStatement, type ProgressCallback, type ProjectConfig, type Pruner, type PrunerItem, type ReembedOptions, type ReembedResult, type ReembedTable, type ReembeddablePlugin, type ResolvedConfig, SQLiteAdapter, SUPPORTED_EXTENSIONS, type SearchHit, type SearchOptions, type SearchResult, type SearchResultType, type SearchStrategy, type SearchablePlugin, type StageProgressCallback, type VectorIndex, type VectorRow, type VectorSearchPlugin, type WatchConfig, type WatchEvent, type WatchEventHandler, type WatchHandle, type WatchOptions, type WatchablePlugin, Watcher, type WebhookHandler, WebhookServer, acquireLock, bumpVersion, contextFromCLI, cosineSimilarity, createBrain, createTracker, escapeLike, getLanguage, getServerUrl, getVersion, getVersions, isBM25SearchPlugin, isCoEditPlugin, isCodeResult, isCollectionResult, isCommitResult, isContextFieldPlugin, isContextFormatterPlugin, isDocsPlugin, isDocumentResult, isExpandablePlugin, isFileResolvable, isIgnoredDir, isIgnoredFile, isIndexable, isMigratable, isReembeddable, isSearchable, isServerRunning, isSupported, isVectorSearchPlugin, isWatchable, matchResult, normalize, normalizeBM25, providerKey, pruneResults, readPid, reciprocalRankFusion, releaseLock, removePid, resetFactoryCache, resolveConfig, resolveEmbedding, runPluginMigrations, sanitizeFTS, searchMMR, vecToBuffer, withLock, writePid };
|