brainbank 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +76 -1398
  2. package/bin/brainbank +5 -1
  3. package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
  4. package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
  5. package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
  6. package/dist/chunk-3JZIM5AU.js.map +1 -0
  7. package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
  8. package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
  9. package/dist/chunk-7JDCHUJV.js +89 -0
  10. package/dist/chunk-7JDCHUJV.js.map +1 -0
  11. package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
  12. package/dist/chunk-7T2ZCZQA.js.map +1 -0
  13. package/dist/chunk-E3J37GDA.js +74 -0
  14. package/dist/chunk-E3J37GDA.js.map +1 -0
  15. package/dist/chunk-JEFWMS5Z.js +217 -0
  16. package/dist/chunk-JEFWMS5Z.js.map +1 -0
  17. package/dist/chunk-JRVYSTMP.js +3256 -0
  18. package/dist/chunk-JRVYSTMP.js.map +1 -0
  19. package/dist/chunk-OLDHLOMT.js +69 -0
  20. package/dist/chunk-OLDHLOMT.js.map +1 -0
  21. package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
  22. package/dist/chunk-QTZNB6AK.js.map +1 -0
  23. package/dist/chunk-RFF7HMP6.js +109 -0
  24. package/dist/chunk-RFF7HMP6.js.map +1 -0
  25. package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
  26. package/dist/chunk-TD3TEFI3.js.map +1 -0
  27. package/dist/cli.js +1036 -341
  28. package/dist/cli.js.map +1 -1
  29. package/dist/haiku-expander-WOVJIVXD.js +8 -0
  30. package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
  31. package/dist/http-server-GIRELCCL.js +9 -0
  32. package/dist/index.d.ts +1774 -611
  33. package/dist/index.js +282 -70
  34. package/dist/index.js.map +1 -1
  35. package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
  36. package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
  37. package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
  38. package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
  39. package/dist/plugin-FF4Q34TI.js +32 -0
  40. package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
  41. package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
  42. package/package.json +25 -52
  43. package/src/brainbank.ts +620 -0
  44. package/src/cli/commands/collection.ts +77 -0
  45. package/src/cli/commands/context.ts +171 -0
  46. package/src/cli/commands/daemon.ts +100 -0
  47. package/src/cli/commands/docs.ts +71 -0
  48. package/src/cli/commands/files.ts +69 -0
  49. package/src/cli/commands/help.ts +72 -0
  50. package/src/cli/commands/index.ts +282 -0
  51. package/src/cli/commands/kv.ts +140 -0
  52. package/src/cli/commands/mcp.ts +13 -0
  53. package/src/cli/commands/reembed.ts +30 -0
  54. package/src/cli/commands/scan.ts +365 -0
  55. package/src/cli/commands/search.ts +130 -0
  56. package/src/cli/commands/stats.ts +44 -0
  57. package/src/cli/commands/status.ts +47 -0
  58. package/src/cli/commands/watch.ts +43 -0
  59. package/src/cli/factory/brain-context.ts +43 -0
  60. package/src/cli/factory/builtin-registration.ts +123 -0
  61. package/src/cli/factory/config-loader.ts +72 -0
  62. package/src/cli/factory/index.ts +65 -0
  63. package/src/cli/factory/plugin-loader.ts +146 -0
  64. package/src/cli/index.ts +63 -0
  65. package/src/cli/server-client.ts +135 -0
  66. package/src/cli/utils.ts +121 -0
  67. package/src/config.ts +50 -0
  68. package/src/constants.ts +13 -0
  69. package/src/db/adapter.ts +112 -0
  70. package/src/db/metadata.ts +130 -0
  71. package/src/db/migrations.ts +66 -0
  72. package/src/db/sqlite-adapter.ts +208 -0
  73. package/src/db/tracker.ts +91 -0
  74. package/src/engine/index-api.ts +85 -0
  75. package/src/engine/reembed.ts +206 -0
  76. package/src/engine/search-api.ts +222 -0
  77. package/src/index.ts +159 -0
  78. package/src/lib/fts.ts +57 -0
  79. package/src/lib/languages.ts +180 -0
  80. package/src/lib/logger.ts +125 -0
  81. package/src/lib/math.ts +87 -0
  82. package/src/lib/provider-key.ts +20 -0
  83. package/src/lib/prune.ts +71 -0
  84. package/src/lib/rerank.ts +33 -0
  85. package/src/lib/rrf.ts +133 -0
  86. package/src/lib/write-lock.ts +108 -0
  87. package/src/plugin.ts +323 -0
  88. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  89. package/src/providers/embeddings/embedding-worker.ts +141 -0
  90. package/src/providers/embeddings/local-embedding.ts +115 -0
  91. package/src/providers/embeddings/openai-embedding.ts +167 -0
  92. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  93. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  94. package/src/providers/embeddings/resolve.ts +34 -0
  95. package/src/providers/pruners/haiku-expander.ts +152 -0
  96. package/src/providers/pruners/haiku-pruner.ts +112 -0
  97. package/src/providers/rerankers/qwen3-reranker.ts +180 -0
  98. package/src/providers/vector/hnsw-index.ts +174 -0
  99. package/src/providers/vector/hnsw-loader.ts +129 -0
  100. package/src/search/bm25-boost.ts +61 -0
  101. package/src/search/context-builder.ts +298 -0
  102. package/src/search/keyword/composite-bm25-search.ts +62 -0
  103. package/src/search/types.ts +35 -0
  104. package/src/search/vector/composite-vector-search.ts +76 -0
  105. package/src/search/vector/mmr.ts +64 -0
  106. package/src/services/collection.ts +405 -0
  107. package/src/services/daemon.ts +87 -0
  108. package/src/services/http-server.ts +288 -0
  109. package/src/services/kv-service.ts +65 -0
  110. package/src/services/plugin-registry.ts +109 -0
  111. package/src/services/watch.ts +348 -0
  112. package/src/services/webhook-server.ts +100 -0
  113. package/src/types.ts +504 -0
  114. package/dist/base-3SNc_CeY.d.ts +0 -593
  115. package/dist/chunk-424UFCY7.js.map +0 -1
  116. package/dist/chunk-7EZR47JV.js +0 -232
  117. package/dist/chunk-7EZR47JV.js.map +0 -1
  118. package/dist/chunk-B77KABWH.js.map +0 -1
  119. package/dist/chunk-CCXVL56V.js.map +0 -1
  120. package/dist/chunk-DI3H6JVZ.js +0 -2432
  121. package/dist/chunk-DI3H6JVZ.js.map +0 -1
  122. package/dist/chunk-FGL32LUJ.js +0 -754
  123. package/dist/chunk-FGL32LUJ.js.map +0 -1
  124. package/dist/chunk-JRSKWF6K.js +0 -313
  125. package/dist/chunk-JRSKWF6K.js.map +0 -1
  126. package/dist/chunk-U2Q2XGPZ.js +0 -42
  127. package/dist/chunk-U2Q2XGPZ.js.map +0 -1
  128. package/dist/chunk-VQ27YUHH.js +0 -629
  129. package/dist/chunk-VQ27YUHH.js.map +0 -1
  130. package/dist/chunk-VVXYZIIB.js +0 -304
  131. package/dist/chunk-VVXYZIIB.js.map +0 -1
  132. package/dist/chunk-YOLKSYWK.js +0 -79
  133. package/dist/chunk-YOLKSYWK.js.map +0 -1
  134. package/dist/chunk-ZNLN2VWV.js.map +0 -1
  135. package/dist/code.d.ts +0 -33
  136. package/dist/code.js +0 -9
  137. package/dist/docs.d.ts +0 -21
  138. package/dist/docs.js +0 -9
  139. package/dist/git.d.ts +0 -33
  140. package/dist/git.js +0 -9
  141. package/dist/memory.d.ts +0 -17
  142. package/dist/memory.js +0 -9
  143. package/dist/notes.d.ts +0 -17
  144. package/dist/notes.js +0 -10
  145. package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
  146. package/dist/resolve-CUJWY6HP.js.map +0 -1
  147. /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
  148. /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
  149. /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
  150. /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
  151. /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
  152. /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
  153. /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
  154. /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
  155. /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
  156. /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,48 +1,1063 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { a as ProgressCallback, B as BrainBankConfig, P as Plugin, C as Collection, S as StageProgressCallback, I as IndexResult, D as DocumentCollection, b as SearchResult, c as ContextOptions, d as CoEditSuggestion, e as IndexStats, R as ResolvedConfig, E as EmbeddingProvider, f as Reranker, V as VectorIndex, g as SearchHit, h as CodeChunk, i as Database, H as HNSWIndex, L as LearningPattern } from './base-3SNc_CeY.js';
3
- export { j as CodeResult, k as CodeResultMetadata, l as CollectionAddOptions, m as CollectionItem, n as CollectionPlugin, o as CollectionResult, p as CollectionSearchOptions, q as CommitResult, r as CommitResultMetadata, s as DistilledStrategy, t as DocChunk, u as DocumentResult, v as DocumentResultMetadata, G as GitCommitRecord, w as IndexablePlugin, x as PatternResult, y as PatternResultMetadata, z as PluginContext, A as SearchResultType, F as SearchablePlugin, W as WatchablePlugin, J as isCodeResult, K as isCollectionResult, M as isCommitResult, N as isDocumentResult, O as isPatternResult, Q as matchResult } from './base-3SNc_CeY.js';
4
- export { code } from './code.js';
5
- export { git } from './git.js';
6
- export { docs } from './docs.js';
7
- export { memory } from './memory.js';
8
- export { notes } from './notes.js';
9
- import 'better-sqlite3';
10
2
 
11
3
  /**
12
- * BrainBank — Watch Mode
4
+ * BrainBank — Database Adapter Interface
13
5
  *
14
- * Auto-indexes on file changes using fs.watch.
15
- * Works with built-in indexers (code, git, docs) and custom indexers.
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.
16
10
  *
17
- * Built-in behavior:
18
- * - Code files re-indexes changed file
19
- * - Doc files → re-indexes changed collection
11
+ * Phase 1: sync-first API matching the existing `better-sqlite3` 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: Buffer;
84
+ }
85
+ interface EmbeddingMetaRow {
86
+ value: string;
87
+ }
88
+ interface VectorRow {
89
+ id: number;
90
+ embedding: Buffer;
91
+ }
92
+ interface CountRow {
93
+ c: number;
94
+ }
95
+
96
+ /**
97
+ * BrainBank — Plugin Migration System
20
98
  *
21
- * Custom indexers:
22
- * - Implement `onFileChange(path, event)` to handle changes
23
- * - Implement `watchPatterns()` to specify which files to watch
99
+ * Per-plugin versioned schema migrations.
100
+ * Each plugin declares a schemaVersion + ordered migrations array.
101
+ * Core stores applied versions in `plugin_versions` table.
24
102
  *
25
- * Usage:
26
- * const watcher = brain.watch({ paths: ['.'] });
27
- * watcher.close(); // stop watching
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.
28
105
  */
29
106
 
30
- interface WatchOptions {
31
- /** Paths to watch. Default: [config.repoPath] */
32
- paths?: string[];
33
- /** Debounce interval in ms. Default: 2000 */
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
+ private _reranker?;
201
+ constructor(_name: string, _db: DatabaseAdapter, _embedding: EmbeddingProvider, _hnsw: HNSWIndex, _vecs: Map<number, Float32Array>, _reranker?: Reranker | undefined);
202
+ /** Collection name. */
203
+ get name(): string;
204
+ /** Add an item. Returns its ID. */
205
+ add(content: string, options?: CollectionAddOptions | Record<string, unknown>): Promise<number>;
206
+ /** Update an item's content (re-embeds). Returns the new ID. */
207
+ update(id: number, content: string, options?: CollectionAddOptions): Promise<number>;
208
+ /** Add multiple items. Returns their IDs. */
209
+ addMany(items: {
210
+ content: string;
211
+ metadata?: Record<string, unknown>;
212
+ tags?: string[];
213
+ ttl?: string;
214
+ }[]): Promise<number[]>;
215
+ /** Search this collection. */
216
+ search(query: string, options?: CollectionSearchOptions): Promise<CollectionItem[]>;
217
+ /** Search and return results as SearchResult[] for use in hybrid search pipelines. */
218
+ searchAsResults(query: string, k: number): Promise<SearchResult[]>;
219
+ /** List items (newest first). */
220
+ list(options?: {
221
+ limit?: number;
222
+ offset?: number;
223
+ tags?: string[];
224
+ }): CollectionItem[];
225
+ /** Count items in this collection. */
226
+ count(): number;
227
+ /** Keep only the N most recent items, remove the rest. */
228
+ trim(options: {
229
+ keep: number;
230
+ }): Promise<{
231
+ removed: number;
232
+ }>;
233
+ /** Remove items older than a duration string (e.g. '30d', '12h'). */
234
+ prune(options: {
235
+ olderThan: string;
236
+ }): Promise<{
237
+ removed: number;
238
+ }>;
239
+ /** Remove a specific item by ID. */
240
+ remove(id: number): void;
241
+ /** Clear all items in this collection. */
242
+ clear(): void;
243
+ private _removeById;
244
+ private _searchVector;
245
+ /** Compute adaptive over-fetch multiplier based on collection density in shared HNSW. */
246
+ private _adaptiveSearchK;
247
+ private _searchBM25;
248
+ private _rowToItem;
249
+ /** Filter results by tags (item must have ALL specified tags). */
250
+ private _filterByTags;
251
+ /** Remove expired items (TTL). Called automatically on search/list. */
252
+ private _pruneExpired;
253
+ }
254
+
255
+ /**
256
+ * BrainBank — Type Definitions
257
+ *
258
+ * All interfaces and types for the semantic knowledge bank.
259
+ */
260
+
261
+ /** Public contract for a KV collection. Plugins depend on this interface, not the concrete class. */
262
+ interface ICollection {
263
+ /** Collection name. */
264
+ readonly name: string;
265
+ /** Add an item. Returns its ID. */
266
+ add(content: string, options?: CollectionAddOptions | Record<string, unknown>): Promise<number>;
267
+ /** Update an item's content (re-embeds). Returns the new ID. */
268
+ update(id: number, content: string, options?: CollectionAddOptions): Promise<number>;
269
+ /** Add multiple items. Returns their IDs. */
270
+ addMany(items: {
271
+ content: string;
272
+ metadata?: Record<string, unknown>;
273
+ tags?: string[];
274
+ ttl?: string;
275
+ }[]): Promise<number[]>;
276
+ /** Search this collection. */
277
+ search(query: string, options?: CollectionSearchOptions): Promise<CollectionItem[]>;
278
+ /** List items (newest first). */
279
+ list(options?: {
280
+ limit?: number;
281
+ offset?: number;
282
+ tags?: string[];
283
+ }): CollectionItem[];
284
+ /** Count items in this collection. */
285
+ count(): number;
286
+ /** Keep only the N most recent items. */
287
+ trim(options: {
288
+ keep: number;
289
+ }): Promise<{
290
+ removed: number;
291
+ }>;
292
+ /** Remove items older than a duration string. */
293
+ prune(options: {
294
+ olderThan: string;
295
+ }): Promise<{
296
+ removed: number;
297
+ }>;
298
+ /** Remove a specific item by ID. */
299
+ remove(id: number): void;
300
+ /** Clear all items in this collection. */
301
+ clear(): void;
302
+ }
303
+ interface BrainBankConfig {
304
+ /** Root path of the repository to index. Default: '.' */
305
+ repoPath?: string;
306
+ /** SQLite database path. Default: '.brainbank/data/brainbank.db' */
307
+ dbPath?: string;
308
+ /** Max git commits to index. Default: 500 */
309
+ gitDepth?: number;
310
+ /** Max file size in bytes to index. Default: 512_000 (500KB) */
311
+ maxFileSize?: number;
312
+ /** Max diff bytes per commit. Default: 8192 */
313
+ maxDiffBytes?: number;
314
+ /** HNSW M parameter (connections per node). Default: 16 */
315
+ hnswM?: number;
316
+ /** HNSW efConstruction (build-time candidates). Default: 200 */
317
+ hnswEfConstruction?: number;
318
+ /** HNSW efSearch (query-time candidates). Default: 50 */
319
+ hnswEfSearch?: number;
320
+ /** Embedding dimensions. Default: 384 */
321
+ embeddingDims?: number;
322
+ /** Max HNSW elements. Default: 2_000_000 */
323
+ maxElements?: number;
324
+ /** Custom embedding provider (default: local WASM model) */
325
+ embeddingProvider?: EmbeddingProvider;
326
+ /** Optional reranker for improved search quality */
327
+ reranker?: Reranker;
328
+ /** Optional LLM noise filter — drops irrelevant results before formatting */
329
+ pruner?: Pruner;
330
+ /** Optional LLM context expander — discovers additional relevant chunks after pruning */
331
+ expander?: Expander;
332
+ /** Port for optional webhook server (enables push-based watch plugins). */
333
+ webhookPort?: number;
334
+ /** Context field defaults from config.json "context" section. */
335
+ contextFields?: Record<string, unknown>;
336
+ }
337
+ interface ResolvedConfig {
338
+ repoPath: string;
339
+ dbPath: string;
340
+ gitDepth: number;
341
+ maxFileSize: number;
342
+ maxDiffBytes: number;
343
+ hnswM: number;
344
+ hnswEfConstruction: number;
345
+ hnswEfSearch: number;
346
+ embeddingDims: number;
347
+ maxElements: number;
348
+ embeddingProvider?: EmbeddingProvider;
349
+ reranker?: Reranker;
350
+ pruner?: Pruner;
351
+ expander?: Expander;
352
+ webhookPort?: number;
353
+ /** Context field defaults from config.json "context" section. */
354
+ contextFields?: Record<string, unknown>;
355
+ }
356
+ interface EmbeddingProvider {
357
+ /** Vector dimensions produced by this provider. */
358
+ readonly dims: number;
359
+ /** Embed a single text string. */
360
+ embed(text: string): Promise<Float32Array>;
361
+ /** Embed multiple texts (batch). */
362
+ embedBatch(texts: string[]): Promise<Float32Array[]>;
363
+ /** Release resources. */
364
+ close(): Promise<void>;
365
+ }
366
+ interface Reranker {
367
+ /**
368
+ * Score each document's relevance to the query.
369
+ * @param query - The search query
370
+ * @param documents - Document contents to rank
371
+ * @returns Relevance scores (0.0 - 1.0) in same order as documents
372
+ */
373
+ rank(query: string, documents: string[]): Promise<number[]>;
374
+ /** Release resources (e.g. unload model). */
375
+ close?(): Promise<void>;
376
+ }
377
+ /** Item passed to the pruner for noise classification. */
378
+ interface PrunerItem {
379
+ /** Positional index (used to map back to SearchResult[]) */
380
+ id: number;
381
+ /** File path — primary signal for relevance */
382
+ filePath: string;
383
+ /** Trimmed content preview */
384
+ preview: string;
385
+ /** Chunk metadata (type, name, language, lines, etc.) */
386
+ metadata: Record<string, unknown>;
387
+ }
388
+ interface Pruner {
389
+ /**
390
+ * Filter noise from search results and order by semantic relevance.
391
+ * @param query - The search query
392
+ * @param items - Items to evaluate (filePath + metadata + trimmed preview)
393
+ * @returns Array of item IDs to KEEP, ordered by relevance to the query
394
+ * (most relevant first). Everything else is dropped.
395
+ */
396
+ prune(query: string, items: PrunerItem[]): Promise<number[]>;
397
+ /** Release resources. */
398
+ close?(): Promise<void>;
399
+ }
400
+ /** Lightweight chunk descriptor for the expander manifest. */
401
+ interface ExpanderManifestItem {
402
+ /** Chunk ID (code_chunks.id) */
403
+ id: number;
404
+ /** File path */
405
+ filePath: string;
406
+ /** Chunk name (e.g. function/class name) */
407
+ name: string;
408
+ /** Chunk type (function, class, method, etc.) */
409
+ chunkType: string;
410
+ /** Line range (e.g. "L45-L89") */
411
+ lines: string;
412
+ }
413
+ /** Result from the expander: chunk IDs to add + optional free-text note. */
414
+ interface ExpanderResult {
415
+ /** Additional chunk IDs to splice into results. */
416
+ ids: number[];
417
+ /** Brief observation for the agent (e.g. "file X not found", "module Y is deprecated"). */
418
+ note?: string;
419
+ }
420
+ interface Expander {
421
+ /**
422
+ * Given a task and a manifest of available chunks,
423
+ * return additional chunk IDs + an optional contextual note.
424
+ * Runs after pruning. Fail-open: errors return empty result.
425
+ *
426
+ * @param query - The search query / task description
427
+ * @param currentIds - IDs of chunks already in results (from search + prune)
428
+ * @param manifest - All available chunks (lightweight descriptors)
429
+ * @returns Chunk IDs to add + optional note
430
+ */
431
+ expand(query: string, currentIds: number[], manifest: ExpanderManifestItem[]): Promise<ExpanderResult>;
432
+ /** Release resources. */
433
+ close?(): Promise<void>;
434
+ }
435
+ interface SearchHit {
436
+ id: number;
437
+ score: number;
438
+ }
439
+ interface VectorIndex {
440
+ /** Initialize the index. Must be called before add/search. */
441
+ init(): Promise<this>;
442
+ /** Add a vector with an integer ID. Idempotent: duplicate IDs are skipped. */
443
+ add(vector: Float32Array, id: number): void;
444
+ /** Mark a vector as deleted so it no longer appears in searches. */
445
+ remove(id: number): void;
446
+ /** Search for k nearest neighbors. */
447
+ search(query: Float32Array, k: number): SearchHit[];
448
+ /** Clear all vectors and reset to empty state. */
449
+ reinit(): void;
450
+ /** Number of vectors in the index. */
451
+ readonly size: number;
452
+ }
453
+ interface CodeChunk {
454
+ /** Auto-incremented DB id (set after insert) */
455
+ id?: number;
456
+ /** Relative file path from repo root */
457
+ filePath: string;
458
+ /** Chunk type: 'file' | 'function' | 'class' | 'block' */
459
+ chunkType: string;
460
+ /** Function/class name (if detected) */
461
+ name?: string;
462
+ /** Start line (1-indexed) */
463
+ startLine: number;
464
+ /** End line (1-indexed, inclusive) */
465
+ endLine: number;
466
+ /** Raw content of the chunk */
467
+ content: string;
468
+ /** Language identifier */
469
+ language: string;
470
+ }
471
+ interface GitCommitRecord {
472
+ id?: number;
473
+ hash: string;
474
+ shortHash: string;
475
+ message: string;
476
+ author: string;
477
+ date: string;
478
+ timestamp: number;
479
+ filesChanged: string[];
480
+ diff?: string;
481
+ additions: number;
482
+ deletions: number;
483
+ isMerge: boolean;
484
+ }
485
+ type SearchResultType = 'code' | 'commit' | 'document' | 'collection';
486
+ interface CodeResultMetadata {
487
+ /** Database chunk ID (used by call graph annotations). */
488
+ id?: number;
489
+ /** File path (may duplicate CodeResult.filePath for metadata-only access). */
490
+ filePath?: string;
491
+ /** Adjacent chunk IDs from the same file (used by context expansion). */
492
+ chunkIds?: number[];
493
+ chunkType: string;
494
+ name?: string;
495
+ startLine: number;
496
+ endLine: number;
497
+ language: string;
498
+ searchType?: string;
499
+ rrfScore?: number;
500
+ }
501
+ interface CommitResultMetadata {
502
+ hash: string;
503
+ shortHash: string;
504
+ author: string;
505
+ date: string;
506
+ files: string[];
507
+ additions?: number;
508
+ deletions?: number;
509
+ diff?: string;
510
+ searchType?: string;
511
+ rrfScore?: number;
512
+ }
513
+ interface DocumentResultMetadata {
514
+ collection?: string;
515
+ title?: string;
516
+ seq?: number;
517
+ path?: string;
518
+ searchType?: string;
519
+ /** Internal chunk ID used by hybrid search to map fused results. */
520
+ chunkId?: number;
521
+ rrfScore?: number;
522
+ }
523
+ interface CodeResult {
524
+ type: 'code';
525
+ score: number;
526
+ filePath: string;
527
+ content: string;
528
+ context?: string;
529
+ metadata: CodeResultMetadata;
530
+ }
531
+ interface CommitResult {
532
+ type: 'commit';
533
+ score: number;
534
+ filePath?: string;
535
+ content: string;
536
+ context?: string;
537
+ metadata: CommitResultMetadata;
538
+ }
539
+ interface DocumentResult {
540
+ type: 'document';
541
+ score: number;
542
+ filePath: string;
543
+ content: string;
544
+ context?: string;
545
+ metadata: DocumentResultMetadata;
546
+ }
547
+ interface CollectionResultMetadata {
548
+ id?: number;
549
+ collection?: string;
550
+ rrfScore?: number;
551
+ [key: string]: unknown;
552
+ }
553
+ interface CollectionResult {
554
+ type: 'collection';
555
+ score: number;
556
+ filePath?: string;
557
+ content: string;
558
+ context?: string;
559
+ metadata: CollectionResultMetadata;
560
+ }
561
+ type SearchResult = CodeResult | CommitResult | DocumentResult | CollectionResult;
562
+ /** Narrow a SearchResult to CodeResult. */
563
+ declare function isCodeResult(r: SearchResult): r is CodeResult;
564
+ /** Narrow a SearchResult to CommitResult. */
565
+ declare function isCommitResult(r: SearchResult): r is CommitResult;
566
+ /** Narrow a SearchResult to DocumentResult. */
567
+ declare function isDocumentResult(r: SearchResult): r is DocumentResult;
568
+ /** Narrow a SearchResult to CollectionResult. */
569
+ declare function isCollectionResult(r: SearchResult): r is CollectionResult;
570
+ type MatchHandlers<T> = {
571
+ code?: (r: CodeResult) => T;
572
+ commit?: (r: CommitResult) => T;
573
+ document?: (r: DocumentResult) => T;
574
+ collection?: (r: CollectionResult) => T;
575
+ _?: (r: SearchResult) => T;
576
+ };
577
+ /**
578
+ * Pattern-match on SearchResult type. Calls the matching handler
579
+ * or the `_` fallback. Returns undefined if no handler matches.
580
+ */
581
+ declare function matchResult<T>(result: SearchResult, handlers: MatchHandlers<T>): T | undefined;
582
+ interface ContextOptions {
583
+ /** Per-source result limits. Built-in: 'code', 'git'. Default: { code: 6, git: 5 } */
584
+ sources?: Record<string, number>;
585
+ /** Files the agent is about to modify (improves co-edit suggestions) */
586
+ affectedFiles?: string[];
587
+ /** Minimum similarity score threshold. Default: 0.25 */
588
+ minScore?: number;
589
+ /** Use MMR for diversity. Default: true */
590
+ useMMR?: boolean;
591
+ /** MMR lambda (0 = diversity, 1 = relevance). Default: 0.7 */
592
+ mmrLambda?: number;
593
+ /** Filter results to files under this path prefix (e.g. 'src/services/'). */
594
+ pathPrefix?: string;
595
+ /** File paths to exclude from results (e.g. files already returned in a previous query). */
596
+ excludeFiles?: Set<string>;
597
+ /** Optional per-request pruner override (e.g. HaikuPruner for LLM noise filtering). */
598
+ pruner?: Pruner;
599
+ /** Caller origin for debug logging. */
600
+ source?: 'cli' | 'mcp' | 'daemon' | 'api';
601
+ /**
602
+ * Context field overrides. Merged on top of config.json "context" defaults.
603
+ * Plugin-defined fields like `lines`, `callTree`, `symbols`, `compact`, `imports`.
604
+ * Example: `{ lines: true, callTree: { depth: 3 }, symbols: true }`
605
+ */
606
+ fields?: Record<string, unknown>;
607
+ }
608
+ interface DocumentCollection {
609
+ /** Collection name (e.g. 'notes', 'docs') */
610
+ name: string;
611
+ /** Directory path to index */
612
+ path: string;
613
+ /** Glob pattern for files (default: all markdown) */
614
+ pattern?: string;
615
+ /** Glob patterns to ignore */
616
+ ignore?: string[];
617
+ /** Context description for this collection */
618
+ context?: string;
619
+ }
620
+ interface DocChunk {
621
+ id?: number;
622
+ /** Collection name */
623
+ collection: string;
624
+ /** Relative file path within the collection */
625
+ filePath: string;
626
+ /** Document title (first heading or filename) */
627
+ title: string;
628
+ /** Chunk content */
629
+ content: string;
630
+ /** Chunk sequence within the document (0, 1, 2...) */
631
+ seq: number;
632
+ /** Character position in original document */
633
+ pos: number;
634
+ /** Content hash for incremental updates */
635
+ contentHash: string;
636
+ }
637
+ /** File-level progress (used by indexers). */
638
+ type ProgressCallback = (file: string, current: number, total: number) => void;
639
+ /** Stage-level progress (used by BrainBank.index() orchestrator). */
640
+ type StageProgressCallback = (stage: string, message: string) => void;
641
+ interface IndexResult {
642
+ indexed: number;
643
+ skipped: number;
644
+ chunks?: number;
645
+ removed?: number;
646
+ }
647
+ interface CoEditSuggestion {
648
+ file: string;
649
+ count: number;
650
+ }
651
+ /** Generalized watch event — works for files, APIs, webhooks. */
652
+ interface WatchEvent {
653
+ /** Event type. 'sync' is for batch/poll sources that don't distinguish CRUD. */
654
+ type: 'create' | 'update' | 'delete' | 'sync';
655
+ /** Unique ID of the changed item (file path, PR#123, PROJ-456, etc.). */
656
+ sourceId: string;
657
+ /** Source descriptor (e.g. 'file', 'github:pr', 'jira:card'). */
658
+ sourceName: string;
659
+ /** Optional raw payload to avoid re-fetching. */
660
+ payload?: unknown;
661
+ }
662
+ /** Callback that plugins invoke when they detect a change. */
663
+ type WatchEventHandler = (event: WatchEvent) => void;
664
+ /** Lifecycle handle returned by WatchablePlugin.watch(). */
665
+ interface WatchHandle {
666
+ /** Stop watching and release resources. */
667
+ stop(): Promise<void>;
668
+ /** Whether the watcher is still active. */
669
+ readonly active: boolean;
670
+ }
671
+ /** Optional hints from plugin to core — debounce, batching, priority. */
672
+ interface WatchConfig {
673
+ /** Debounce interval in ms. 0 = process immediately. Default: inherited from WatchOptions. */
34
674
  debounceMs?: number;
35
- /** Called when a file is re-indexed. */
36
- onIndex?: (filePath: string, indexer: string) => void;
37
- /** Called on errors. */
38
- onError?: (error: Error) => void;
675
+ /** Max events to batch before triggering re-index. Default: unlimited. */
676
+ batchSize?: number;
677
+ /** Processing priority. Default: 'realtime'. */
678
+ priority?: 'realtime' | 'background';
39
679
  }
40
- interface Watcher {
41
- /** Stop watching. */
680
+
681
+ /**
682
+ * BrainBank — HNSW Vector Index
683
+ *
684
+ * Wraps hnswlib-node for O(log n) approximate nearest neighbor search.
685
+ * M=16 connections, ef=200 construction, ef=50 search by default.
686
+ * 150x faster than brute force at 1M vectors.
687
+ *
688
+ * Supports disk persistence: save(path) / tryLoad(path, count)
689
+ * to skip costly vector-by-vector rebuild on startup.
690
+ */
691
+
692
+ declare class HNSWIndex implements VectorIndex {
693
+ private _dims;
694
+ private _maxElements;
695
+ private _M;
696
+ private _efConstruction;
697
+ private _efSearch;
698
+ private _index;
699
+ private _lib;
700
+ private _ids;
701
+ constructor(_dims: number, _maxElements?: number, _M?: number, _efConstruction?: number, _efSearch?: number);
702
+ /**
703
+ * Initialize the HNSW index.
704
+ * Must be called before add/search.
705
+ */
706
+ init(): Promise<this>;
707
+ /**
708
+ * Reinitialize the index in-place, clearing all vectors.
709
+ * Required after reembed or full re-index to avoid duplicate IDs.
710
+ * init() must have been called first.
711
+ */
712
+ reinit(): void;
713
+ private _createIndex;
714
+ /** Maximum capacity of this index. */
715
+ get maxElements(): number;
716
+ /**
717
+ * Add a vector with an integer ID.
718
+ * The vector should be pre-normalized for cosine distance.
719
+ */
720
+ add(vector: Float32Array, id: number): void;
721
+ /**
722
+ * Mark a vector as deleted so it no longer appears in searches.
723
+ * Uses hnswlib-node markDelete under the hood.
724
+ * Safe to call with an ID that doesn't exist.
725
+ */
726
+ remove(id: number): void;
727
+ /**
728
+ * Search for the k nearest neighbors.
729
+ * Returns results sorted by score (highest first).
730
+ * Score is 1 - cosine_distance (1.0 = identical).
731
+ */
732
+ search(query: Float32Array, k: number): SearchHit[];
733
+ /** Number of vectors in the index. */
734
+ get size(): number;
735
+ /**
736
+ * Save the HNSW graph to disk.
737
+ * The file can be loaded later with tryLoad() to skip vector-by-vector insertion.
738
+ */
739
+ save(path: string): void;
740
+ /**
741
+ * Try to load a previously saved HNSW index from disk.
742
+ * Returns true if loaded successfully, false if stale or missing.
743
+ * @param path File path to the saved index
744
+ * @param expectedCount Expected number of vectors (from SQLite) — used to detect staleness
745
+ */
746
+ tryLoad(path: string, expectedCount: number): boolean;
747
+ }
748
+
749
+ /**
750
+ * BrainBank — Search Types
751
+ *
752
+ * Shared interface for all search strategies.
753
+ * Implement SearchStrategy to add a new search backend.
754
+ */
755
+
756
+ /** Any search implementation follows this shape. */
757
+ interface SearchStrategy {
758
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
759
+ /** Rebuild internal indices (e.g. FTS5). Optional. */
760
+ rebuild?(): void;
761
+ }
762
+ /** Pre-embedded vector search for a single domain (code, git, etc.). */
763
+ interface DomainVectorSearch {
764
+ /** Search using a pre-computed query vector. Optional queryText enables BM25 fusion. */
765
+ search(queryVec: Float32Array, k: number, minScore: number, useMMR?: boolean, mmrLambda?: number, queryText?: string): SearchResult[];
766
+ }
767
+ interface SearchOptions {
768
+ /** Per-source result limits. Built-in: 'code', 'git', 'memory'. Any other key = custom plugin or KV collection. */
769
+ sources?: Record<string, number>;
770
+ /** Minimum similarity score. Default: 0.25 */
771
+ minScore?: number;
772
+ /** Use MMR for diversity. Default: true */
773
+ useMMR?: boolean;
774
+ /** MMR lambda. Default: 0.7 */
775
+ mmrLambda?: number;
776
+ /** Caller origin for debug logging. */
777
+ source?: 'cli' | 'mcp' | 'daemon' | 'api';
778
+ }
779
+
780
+ /**
781
+ * BrainBank — Webhook Server
782
+ *
783
+ * Optional shared HTTP server for push-based watch plugins (e.g. Jira, GitHub).
784
+ * Opt-in: only created when `new BrainBank({ webhookPort: 4242 })` is configured.
785
+ *
786
+ * Plugins register routes during `watch()`:
787
+ * ctx.webhookServer?.register('jira', '/jira/webhook', handler);
788
+ *
789
+ * Each plugin gets its own path namespace. Unregistering cleans up the route.
790
+ */
791
+ /** Handler for incoming webhook payloads. */
792
+ type WebhookHandler = (body: unknown) => void;
793
+ /** Shared HTTP server for push-based watch plugins. */
794
+ declare class WebhookServer {
795
+ private _server;
796
+ private _routes;
797
+ private _listening;
798
+ /** Start listening on the specified port. */
799
+ listen(port: number): void;
800
+ /** Register a webhook route for a plugin. */
801
+ register(pluginName: string, path: string, handler: WebhookHandler): void;
802
+ /** Remove all routes for a plugin. */
803
+ unregister(pluginName: string): void;
804
+ /** Stop the server and clear all routes. */
42
805
  close(): void;
43
- /** Whether the watcher is active. */
44
- readonly active: boolean;
806
+ /** Whether the server is currently listening. */
807
+ get active(): boolean;
808
+ /** Route incoming POST requests to the matching handler. */
809
+ private _handleRequest;
810
+ }
811
+
812
+ /**
813
+ * BrainBank — Plugin System
814
+ *
815
+ * Plugins are pluggable strategies that scan external data sources
816
+ * and push content into BrainBank. Built-in plugins handle code,
817
+ * git, and docs. Third-party frameworks (LangChain, etc.)
818
+ * can implement custom plugins.
819
+ *
820
+ * import { BrainBank } from 'brainbank';
821
+ * import { code } from 'brainbank/indexers/code';
822
+ *
823
+ * const brain = new BrainBank()
824
+ * .use(code({ repoPath: '.' }));
825
+ */
826
+
827
+ interface PluginContext {
828
+ /** Database adapter (shared across all plugins). */
829
+ db: DatabaseAdapter;
830
+ /** Embedding provider (shared). */
831
+ embedding: EmbeddingProvider;
832
+ /** Resolved BrainBank config. */
833
+ config: ResolvedConfig;
834
+ /**
835
+ * Create and initialize an HNSW index.
836
+ * Pass `name` to enable disk persistence (recommended).
837
+ *
838
+ * **Private vs shared:** Use `getOrCreateSharedHnsw()` for indexes that should be
839
+ * part of the composite search (code, git) and persisted across restarts.
840
+ * Use `createHnsw()` for plugin-local indexes that don't participate in the
841
+ * main search pipeline (e.g. internal similarity lookups).
842
+ */
843
+ createHnsw(maxElements?: number, dims?: number, name?: string): Promise<HNSWIndex>;
844
+ /** Load existing vectors from a SQLite vectors table into an HNSW index + cache. */
845
+ loadVectors(table: string, idCol: string, hnsw: HNSWIndex, cache: Map<number, Float32Array>): void;
846
+ /**
847
+ * Get or create a shared HNSW index by key.
848
+ *
849
+ * **HNSW sharing strategies:**
850
+ * The `type` key determines sharing behavior. Two plugins that pass the
851
+ * same key share one HNSW index; different keys get separate indexes.
852
+ *
853
+ * | Plugin type | Key passed | Sharing behavior |
854
+ * |------------|------------------|------------------------------------------|
855
+ * | git | `'git'` | All `git:*` repos share one HNSW |
856
+ * | docs | `'docs'` | All docs share one HNSW |
857
+ * | code | `this.name` | Each `code:*` repo gets its own HNSW |
858
+ *
859
+ * **Rule of thumb:**
860
+ * - Same key = shared index (saves memory, single search covers all)
861
+ * - Plugin name as key = per-repo index (avoids cross-repo noise)
862
+ *
863
+ * The key is also used for hot-reload (`ensureFresh`) and disk persistence
864
+ * (`hnsw-<key>.index`), so it must match the key used in `bumpVersion()`.
865
+ */
866
+ getOrCreateSharedHnsw(type: string, maxElements?: number, dims?: number): Promise<{
867
+ hnsw: HNSWIndex;
868
+ vecCache: Map<number, Float32Array>;
869
+ isNew: boolean;
870
+ }>;
871
+ /** Get or create a dynamic collection. */
872
+ collection(name: string): ICollection;
873
+ /**
874
+ * Create an incremental tracker scoped to this plugin.
875
+ * Provides `isUnchanged`, `markIndexed`, `findOrphans`, `remove`, `clear`
876
+ * for standardized add/update/delete detection during indexing.
877
+ */
878
+ createTracker(): IncrementalTracker;
879
+ /** Optional webhook server for push-based watch plugins. undefined if not configured. */
880
+ webhookServer?: WebhookServer;
881
+ }
882
+ interface Plugin {
883
+ /** Unique plugin name (e.g. 'code', 'git', 'docs'). */
884
+ readonly name: string;
885
+ /** Initialize the plugin (create HNSW, load vectors, etc.). */
886
+ initialize(ctx: PluginContext): Promise<void>;
887
+ /** Return stats for this plugin. */
888
+ stats?(): Record<string, number | string>;
889
+ /** Clean up resources. */
890
+ close?(): void;
891
+ }
892
+ /** Options accepted by IndexablePlugin.index(). */
893
+ interface IndexOptions {
894
+ forceReindex?: boolean;
895
+ depth?: number;
896
+ onProgress?: ProgressCallback;
897
+ }
898
+ /** Plugins that can scan and index content (code, git). */
899
+ interface IndexablePlugin extends Plugin {
900
+ index(options?: IndexOptions): Promise<IndexResult>;
901
+ /** Incremental: re-index only specific items by ID. Falls back to index() if not implemented. */
902
+ indexItems?(ids: string[]): Promise<IndexResult>;
903
+ }
904
+ /** Plugins that can search indexed content (docs). */
905
+ interface SearchablePlugin extends Plugin {
906
+ search(query: string, options?: Record<string, unknown>): Promise<SearchResult[]>;
907
+ }
908
+ /** Plugins that can watch their own data source for changes. */
909
+ interface WatchablePlugin extends Plugin {
910
+ /** Start watching. Plugin controls how (fs.watch, polling, webhook, etc.). */
911
+ watch(onEvent: WatchEventHandler): WatchHandle;
912
+ /** Optional hints for the core (debounce, batching, priority). */
913
+ watchConfig?(): WatchConfig;
914
+ }
915
+ /** Check if a plugin can scan/index content. */
916
+ declare function isIndexable(i: Plugin): i is IndexablePlugin;
917
+ /** Check if a plugin can search content. */
918
+ declare function isSearchable(i: Plugin): i is SearchablePlugin;
919
+ /** Check if a plugin can watch its own data source. */
920
+ declare function isWatchable(i: Plugin): i is WatchablePlugin;
921
+ /** Path-specific context metadata for document collections. */
922
+ interface PathContext {
923
+ collection: string;
924
+ path: string;
925
+ context: string;
926
+ }
927
+ /** Plugins that manage document collections (docs). */
928
+ interface DocsPlugin extends SearchablePlugin {
929
+ addCollection(collection: DocumentCollection): void;
930
+ removeCollection(name: string): void;
931
+ listCollections(): DocumentCollection[];
932
+ indexDocs(options?: {
933
+ onProgress?: (collection: string, file: string, current: number, total: number) => void;
934
+ }): Promise<Record<string, {
935
+ indexed: number;
936
+ skipped: number;
937
+ removed: number;
938
+ chunks: number;
939
+ }>>;
940
+ addContext(collection: string, path: string, context: string): void;
941
+ listContexts(): PathContext[];
942
+ }
943
+ /** Check if a plugin manages document collections. */
944
+ declare function isDocsPlugin(i: Plugin): i is DocsPlugin;
945
+ /** Plugin that provides co-edit suggestions (e.g. git). */
946
+ interface CoEditPlugin extends Plugin {
947
+ coEdits: {
948
+ suggest(filePath: string, limit: number): {
949
+ file: string;
950
+ count: number;
951
+ }[];
952
+ };
45
953
  }
954
+ /** Check if a plugin provides co-edit suggestions. */
955
+ declare function isCoEditPlugin(p: Plugin): p is CoEditPlugin;
956
+ /** Table descriptor for re-embedding — maps text rows to vector BLOBs. */
957
+ interface ReembedTable {
958
+ /** Human-readable name (for progress). */
959
+ name: string;
960
+ /** Table with text content. */
961
+ textTable: string;
962
+ /** Table with vector BLOBs. */
963
+ vectorTable: string;
964
+ /** PK column in text table. */
965
+ idColumn: string;
966
+ /** FK column in vector table. */
967
+ fkColumn: string;
968
+ /** Build the embedding text from a DB row. */
969
+ textBuilder: (row: Record<string, unknown>) => string;
970
+ }
971
+ /** Plugins that own vector tables and can rebuild embedding text from DB rows. */
972
+ interface ReembeddablePlugin extends Plugin {
973
+ /** Table descriptor for re-embedding. */
974
+ reembedConfig(): ReembedTable;
975
+ }
976
+ /** Check if a plugin supports re-embedding. */
977
+ declare function isReembeddable(p: Plugin): p is ReembeddablePlugin;
978
+ /** Plugin that provides a domain-specific vector search strategy. */
979
+ interface VectorSearchPlugin extends Plugin {
980
+ /** Create the domain vector search (called during SearchAPI wiring). */
981
+ createVectorSearch(): DomainVectorSearch | undefined;
982
+ }
983
+ /** Check if a plugin provides a domain vector search. */
984
+ declare function isVectorSearchPlugin(p: Plugin): p is VectorSearchPlugin;
985
+ /** Describes a configurable context field that a plugin supports. */
986
+ interface ContextFieldDef {
987
+ /** Field name (e.g. 'lines', 'callTree', 'symbols'). Must be unique per plugin. */
988
+ name: string;
989
+ /** Accepted value type. 'object' allows nested config like `{ depth: 3 }`. */
990
+ type: 'boolean' | 'number' | 'object';
991
+ /** Default value (used when not specified in config or query). */
992
+ default: unknown;
993
+ /** Human-readable description for CLI --help and MCP tool descriptions. */
994
+ description: string;
995
+ }
996
+ /** Plugin that declares configurable context fields. */
997
+ interface ContextFieldPlugin extends Plugin {
998
+ /** Declare available context fields. Called once during setup. */
999
+ contextFields(): ContextFieldDef[];
1000
+ }
1001
+ /** Check if a plugin declares context fields. */
1002
+ declare function isContextFieldPlugin(p: Plugin): p is ContextFieldPlugin;
1003
+ /** Plugin that contributes sections to the context builder output. */
1004
+ interface ContextFormatterPlugin extends Plugin {
1005
+ /**
1006
+ * Append formatted markdown sections to `parts`.
1007
+ * `fields` contains resolved context fields (plugin defaults ← config ← per-query).
1008
+ */
1009
+ formatContext(results: SearchResult[], parts: string[], fields: Record<string, unknown>): void;
1010
+ }
1011
+ /** Check if a plugin provides context formatting. */
1012
+ declare function isContextFormatterPlugin(p: Plugin): p is ContextFormatterPlugin;
1013
+ /** Plugin that owns database tables and supports versioned migrations. */
1014
+ interface MigratablePlugin extends Plugin {
1015
+ /** Current schema version for this plugin. */
1016
+ readonly schemaVersion: number;
1017
+ /** Ordered list of migrations (version 1, 2, 3, …). */
1018
+ readonly migrations: Migration[];
1019
+ }
1020
+ /** Check if a plugin supports schema migrations. */
1021
+ declare function isMigratable(p: Plugin): p is MigratablePlugin;
1022
+ /** Plugin that can do FTS5 keyword search on its own tables. */
1023
+ interface BM25SearchPlugin extends Plugin {
1024
+ /** Run BM25 keyword search. Returns scored results. */
1025
+ searchBM25(query: string, k: number, minScore?: number): SearchResult[];
1026
+ /** Rebuild the FTS5 index from the content table. */
1027
+ rebuildFTS?(): void;
1028
+ }
1029
+ /** Check if a plugin provides BM25 keyword search. */
1030
+ declare function isBM25SearchPlugin(p: Plugin): p is BM25SearchPlugin;
1031
+ /** Plugin that supports context expansion (provides manifest + resolves chunk IDs). */
1032
+ interface ExpandablePlugin extends Plugin {
1033
+ /**
1034
+ * Build a manifest of candidate chunks for LLM expansion.
1035
+ * Returns chunks from files NOT already in search results.
1036
+ *
1037
+ * @param excludeFilePaths File paths already present in search results — excluded from manifest.
1038
+ * @param excludeIds Chunk IDs already in search results — excluded from manifest.
1039
+ */
1040
+ buildManifest(excludeFilePaths: string[], excludeIds: number[]): ExpanderManifestItem[];
1041
+ /**
1042
+ * Resolve chunk IDs back into SearchResults.
1043
+ * Called after the expander selects additional IDs.
1044
+ */
1045
+ resolveChunks(ids: number[]): SearchResult[];
1046
+ }
1047
+ /** Check if a plugin supports context expansion. */
1048
+ declare function isExpandablePlugin(p: Plugin): p is ExpandablePlugin;
1049
+ /** Plugin that can resolve file paths/patterns directly to SearchResults (no search). */
1050
+ interface FileResolvablePlugin extends Plugin {
1051
+ /**
1052
+ * Resolve file paths, directories, and glob patterns to SearchResults.
1053
+ * Each entry is resolved: exact → directory → glob → fuzzy basename fallback.
1054
+ *
1055
+ * @param patterns - File paths, directory prefixes (trailing `/`), or glob patterns (`*`).
1056
+ */
1057
+ resolveFiles(patterns: string[]): SearchResult[];
1058
+ }
1059
+ /** Check if a plugin can resolve files directly. */
1060
+ declare function isFileResolvable(p: Plugin): p is FileResolvablePlugin;
46
1061
 
47
1062
  /**
48
1063
  * BrainBank — Re-embedding Engine
@@ -53,16 +1068,12 @@ interface Watcher {
53
1068
  *
54
1069
  * Usage:
55
1070
  * const result = await brain.reembed({ onProgress });
56
- * // → { code: 1200, git: 500, docs: 80, kv: 45, notes: 12, total: 1837 }
1071
+ * // → { code: 1200, git: 500, docs: 80, kv: 45, total: 1837 }
57
1072
  */
58
1073
 
59
1074
  interface ReembedResult {
60
- code: number;
61
- git: number;
62
- memory: number;
63
- notes: number;
64
- docs: number;
65
- kv: number;
1075
+ /** Per-table vector counts. Keys are table names (e.g. 'code', 'git', 'docs', 'kv'). */
1076
+ counts: Record<string, number>;
66
1077
  total: number;
67
1078
  }
68
1079
  interface ReembedOptions {
@@ -72,17 +1083,79 @@ interface ReembedOptions {
72
1083
  batchSize?: number;
73
1084
  }
74
1085
 
1086
+ /**
1087
+ * BrainBank — Watcher
1088
+ *
1089
+ * Thin coordinator for plugin-driven watching. Each plugin CAN drive its own
1090
+ * watching via WatchablePlugin.watch(). For IndexablePlugins that don't
1091
+ * implement WatchablePlugin, the Watcher provides a single shared fs.watch
1092
+ * tree with fan-out routing so each plugin only receives relevant events.
1093
+ *
1094
+ * Responsibilities:
1095
+ * 1. Call `plugin.watch(onEvent)` for each WatchablePlugin
1096
+ * 2. For IndexablePlugins without watch(), share one recursive fs.watch tree
1097
+ * 3. Route events to the correct plugin based on sub-repo scope
1098
+ * 4. Dedup macOS double-fire events (change+rename per save)
1099
+ * 5. Apply per-plugin debounce from `plugin.watchConfig()`
1100
+ * 6. On event: call `plugin.indexItems([id])` or `plugin.index()` for re-indexing
1101
+ * 7. Call `handle.stop()` on `close()`
1102
+ *
1103
+ * const watcher = brain.watch({ debounceMs: 2000 });
1104
+ * watcher.close(); // stop watching
1105
+ */
1106
+
1107
+ interface WatchOptions {
1108
+ /** Default debounce for plugins that don't specify watchConfig. Default: 2000 */
1109
+ debounceMs?: number;
1110
+ /** Glob patterns to ignore (from config.json code.ignore). */
1111
+ ignore?: string[];
1112
+ /** Called when a source triggers re-indexing. */
1113
+ onIndex?: (sourceId: string, pluginName: string) => void;
1114
+ /** Called on errors. */
1115
+ onError?: (error: Error) => void;
1116
+ }
1117
+ /** Plugin-driven watcher that coordinates re-indexing across all WatchablePlugins. */
1118
+ declare class Watcher {
1119
+ private _active;
1120
+ private _batches;
1121
+ private _reindexFn;
1122
+ private _options;
1123
+ private _keepalive;
1124
+ constructor(reindexFn: () => Promise<void>, plugins: Plugin[], options?: WatchOptions, repoPath?: string);
1125
+ /** Whether the watcher is active. */
1126
+ get active(): boolean;
1127
+ /** Stop all plugin watchers. */
1128
+ close(): Promise<void>;
1129
+ /** Start watching for each WatchablePlugin, with shared fs.watch fallback. */
1130
+ private _startWatching;
1131
+ /**
1132
+ * Single shared recursive fs.watch that fans out events to multiple plugins.
1133
+ * Each event is routed based on: sub-repo prefix (code:backend → servicehub-backend/),
1134
+ * file extension (docs → .md only), and code support (code → isSupported).
1135
+ */
1136
+ private _startSharedFsWatch;
1137
+ /** Handle an incoming event from a plugin. */
1138
+ private _onEvent;
1139
+ /** Flush pending events for a plugin — trigger re-indexing. */
1140
+ private _flush;
1141
+ }
1142
+
75
1143
  /**
76
1144
  * BrainBank — Main Orchestrator
77
1145
  *
78
- * Thin facade that composes four services:
79
- * PluginRegistry — registration + lookup
80
- * Initializer two-phase startup (earlyInit / lateInit)
81
- * SearchAPI all search + context logic
82
- * IndexAPI — code / git / docs indexing orchestration
1146
+ * Thin facade that composes services:
1147
+ * - **PluginRegistry** — registration + lookup
1148
+ * - **SearchAPI** all search + context logic
1149
+ * - **runIndex** code / git / docs indexing orchestration
83
1150
  *
1151
+ * Initialization is inline — no indirection layers.
84
1152
  * All heavy logic lives in those modules; BrainBank owns state,
85
- * guards (requireInit / initialize()), and public API shape.
1153
+ * guards (`_requireInit` / `initialize`), and public API shape.
1154
+ *
1155
+ * Multi-process coordination:
1156
+ * - `ensureFresh()` detects stale HNSW indices via `index_state` table
1157
+ * - Hot-reloads from disk when another process updated the index
1158
+ * - Called implicitly before every search operation
86
1159
  */
87
1160
 
88
1161
  declare class BrainBank extends EventEmitter {
@@ -91,168 +1164,167 @@ declare class BrainBank extends EventEmitter {
91
1164
  private _embedding;
92
1165
  private _registry;
93
1166
  private _searchAPI?;
94
- private _indexAPI?;
1167
+ private _indexDeps?;
1168
+ private _kvService?;
95
1169
  private _initialized;
96
1170
  private _initPromise;
97
1171
  private _watcher?;
98
- private _collections;
99
- private _kvHnsw?;
100
- private _kvVecs;
1172
+ private _webhookServer?;
101
1173
  private _sharedHnsw;
1174
+ private _repoDBs;
1175
+ private _loadedVersions;
102
1176
  constructor(config?: BrainBankConfig);
1177
+ /** Whether the brainbank has been initialized. */
1178
+ get isInitialized(): boolean;
1179
+ /** The resolved configuration. */
1180
+ get config(): Readonly<ResolvedConfig>;
1181
+ /** All registered plugin names (insertion order). */
1182
+ get plugins(): string[];
103
1183
  /**
104
1184
  * Register a plugin. Chainable.
105
1185
  *
106
- * brain.use(code({ repoPath: '.' })).use(docs());
1186
+ * @example
1187
+ * brain.use(code({ repoPath: '.' })).use(docs());
1188
+ *
1189
+ * @throws If called after `initialize()`.
107
1190
  */
108
1191
  use(plugin: Plugin): this;
109
- /** Get the list of registered plugin names. */
110
- get plugins(): string[];
111
- /** Check if a plugin is loaded. Also matches type prefix (e.g. 'code' matches 'code:frontend'). */
1192
+ /**
1193
+ * Check if a plugin is loaded.
1194
+ * Also matches type prefix (e.g. `'code'` matches `'code:frontend'`).
1195
+ */
112
1196
  has(name: string): boolean;
113
- /** Get a plugin instance. Throws if not loaded. */
114
- plugin<T extends Plugin = Plugin>(n: string): T;
1197
+ /** Get a plugin instance by name. Returns `undefined` if not loaded. */
1198
+ plugin<T extends Plugin = Plugin>(name: string): T | undefined;
115
1199
  /**
116
1200
  * Initialize database, HNSW indices, and load existing vectors.
117
- * Only initializes registered modules.
118
- * Automatically called by index/search methods if not yet initialized.
1201
+ * Automatically called by `index` / `search` methods if not yet initialized.
1202
+ * Concurrent calls are deduped via `_initPromise`.
1203
+ *
1204
+ * @param options.force - If `true`, skip vector load on dimension mismatch.
119
1205
  */
120
1206
  initialize(options?: {
121
1207
  force?: boolean;
122
1208
  }): Promise<void>;
123
- private _runInitialize;
124
1209
  /**
125
- * Get or create a dynamic collection.
126
- * Collections are the universal data primitive store anything, search semantically.
1210
+ * Estimated memory footprint of loaded HNSW indices (bytes).
1211
+ * Counts only vector data: `vectorCount × dims × 4`.
1212
+ * Returns 0 if not initialized.
1213
+ */
1214
+ memoryHint(): number;
1215
+ /** Close database and release all resources. Synchronous. */
1216
+ close(): void;
1217
+ /**
1218
+ * Get or create a dynamic collection (universal KV primitive).
127
1219
  *
128
- * const errors = brain.collection('debug_errors');
129
- * await errors.add('Fixed null check', { file: 'api.ts' });
130
- * const hits = await errors.search('null pointer');
1220
+ * @example
1221
+ * const errors = brain.collection('debug_errors');
1222
+ * await errors.add('Fixed null check', { file: 'api.ts' });
1223
+ * const hits = await errors.search('null pointer');
1224
+ *
1225
+ * @throws If not initialized.
131
1226
  */
132
- collection(name: string): Collection;
1227
+ collection(name: string): ICollection;
133
1228
  /** List all collection names that have data. */
134
1229
  listCollectionNames(): string[];
135
1230
  /** Delete a collection's data and evict from cache. */
136
1231
  deleteCollection(name: string): void;
1232
+ /** Run indexing across selected modules. Auto-initializes. */
137
1233
  index(options?: {
138
- modules?: ('code' | 'git' | 'docs')[];
139
- gitDepth?: number;
1234
+ modules?: string[];
140
1235
  forceReindex?: boolean;
141
1236
  onProgress?: StageProgressCallback;
142
- }): Promise<{
143
- code?: IndexResult;
144
- git?: IndexResult;
145
- docs?: Record<string, {
146
- indexed: number;
147
- skipped: number;
148
- chunks: number;
149
- }>;
150
- }>;
151
- /** Index only code files (all repos in multi-repo mode). */
152
- indexCode(options?: {
153
- forceReindex?: boolean;
154
- onProgress?: ProgressCallback;
155
- }): Promise<IndexResult>;
156
- /** Index only git history (all repos in multi-repo mode). */
157
- indexGit(options?: {
158
- depth?: number;
159
- onProgress?: ProgressCallback;
160
- }): Promise<IndexResult>;
161
- /** Register a document collection. */
162
- addCollection(collection: DocumentCollection): Promise<void>;
163
- /** Remove a collection and all its indexed data. */
164
- removeCollection(name: string): Promise<void>;
165
- /** List all registered collections. */
166
- listCollections(): DocumentCollection[];
167
- /** Index all (or specific) document collections. */
168
- indexDocs(options?: {
169
- collections?: string[];
170
- onProgress?: (collection: string, file: string, current: number, total: number) => void;
171
- }): Promise<Record<string, {
172
- indexed: number;
173
- skipped: number;
174
- chunks: number;
175
- }>>;
176
- /** Search documents only. */
177
- searchDocs(query: string, options?: {
178
- collection?: string;
179
- k?: number;
180
- minScore?: number;
181
- }): Promise<SearchResult[]>;
182
- /** Add context description for a collection path. */
183
- addContext(collection: string, path: string, context: string): void;
184
- /** Remove context for a collection path. */
185
- removeContext(collection: string, path: string): void;
186
- /** List all context entries. */
187
- listContexts(): {
188
- collection: string;
189
- path: string;
190
- context: string;
191
- }[];
1237
+ pluginOptions?: Record<string, unknown>;
1238
+ }): Promise<Record<string, unknown>>;
192
1239
  /**
193
- * Get formatted context for a task.
194
- * Returns markdown ready for system prompt injection.
1240
+ * Detect stale HNSW indices and hot-reload from disk.
1241
+ * Called implicitly before every search operation.
1242
+ * Cost: one SQLite SELECT (~5μs on WAL mode).
195
1243
  */
196
- getContext(task: string, options?: ContextOptions): Promise<string>;
197
- /** Semantic search across all loaded modules. */
198
- search(query: string, options?: {
199
- codeK?: number;
200
- gitK?: number;
201
- patternK?: number;
202
- minScore?: number;
203
- useMMR?: boolean;
204
- }): Promise<SearchResult[]>;
205
- /** Semantic search over code only. */
206
- searchCode(query: string, k?: number): Promise<SearchResult[]>;
207
- /** Semantic search over commits only. */
208
- searchCommits(query: string, k?: number): Promise<SearchResult[]>;
1244
+ ensureFresh(): Promise<void>;
1245
+ /**
1246
+ * Semantic search across all loaded modules.
1247
+ * Scope via `sources: { code: 10, git: 0 }`.
1248
+ */
1249
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
209
1250
  /**
210
1251
  * Hybrid search: vector + BM25 fused with Reciprocal Rank Fusion.
211
- * Best quality catches both exact keyword matches and conceptual similarities.
1252
+ * Scope via `sources: { code: 10, git: 5, docs: 3, myNotes: 5 }`.
212
1253
  */
213
- hybridSearch(query: string, options?: {
214
- codeK?: number;
215
- gitK?: number;
216
- patternK?: number;
217
- minScore?: number;
218
- useMMR?: boolean;
219
- collections?: Record<string, number>;
220
- }): Promise<SearchResult[]>;
1254
+ hybridSearch(query: string, options?: SearchOptions): Promise<SearchResult[]>;
221
1255
  /** BM25 keyword search only (no embeddings needed). */
222
- searchBM25(query: string, options?: {
223
- codeK?: number;
224
- gitK?: number;
225
- patternK?: number;
226
- }): Promise<SearchResult[]>;
227
- /** Rebuild FTS5 indices. */
228
- rebuildFTS(): void;
229
- /** Get git history for a specific file. */
230
- fileHistory(filePath: string, limit?: number): Promise<Record<string, unknown>[]>;
231
- /** Get co-edit suggestions for a file. */
232
- coEdits(filePath: string, limit?: number): CoEditSuggestion[];
233
- /** Get statistics for all loaded modules. */
234
- stats(): IndexStats;
1256
+ searchBM25(query: string, options?: SearchOptions): Promise<SearchResult[]>;
1257
+ /** Build formatted context block for LLM system prompt injection. Auto-initializes. */
1258
+ getContext(task: string, options?: ContextOptions): Promise<string>;
235
1259
  /**
236
- * Start watching for file changes and auto-re-index.
237
- * Works with built-in and custom indexers.
1260
+ * Resolve file paths, directories, and glob patterns to full SearchResults.
1261
+ * Bypasses search entirely reads directly from plugin indexes.
1262
+ *
1263
+ * @example
1264
+ * const files = brain.resolveFiles(['src/auth/login.ts', 'src/graph/']);
238
1265
  */
1266
+ resolveFiles(patterns: string[]): SearchResult[];
1267
+ /** Rebuild FTS5 indices. */
1268
+ rebuildFTS(): void;
1269
+ /** Get statistics for all loaded plugins. */
1270
+ stats(): Record<string, Record<string, number | string> | undefined>;
1271
+ /** Start watching for changes and auto-re-index. */
239
1272
  watch(options?: WatchOptions): Watcher;
240
1273
  /**
241
1274
  * Re-embed all existing text with the current embedding provider.
242
- * Use this when switching providers (e.g. Local → OpenAI).
1275
+ * Use after switching providers (e.g. Local → OpenAI).
243
1276
  */
244
1277
  reembed(options?: ReembedOptions): Promise<ReembedResult>;
245
- /** Close database and release resources. */
246
- close(): void;
247
- /** Whether the brainbank has been initialized. */
248
- get isInitialized(): boolean;
249
- /** The resolved configuration. */
250
- get config(): Readonly<ResolvedConfig>;
1278
+ /**
1279
+ * Linear 8-step initialization:
1280
+ * 1. Open database
1281
+ * 2. Resolve embedding provider
1282
+ * 3. Check dimension mismatch
1283
+ * 4. Create KV HNSW + KVService
1284
+ * 5. Load KV vectors
1285
+ * 6. Initialize plugins
1286
+ * 7. Persist HNSW indices
1287
+ * 8. Build SearchAPI + index deps
1288
+ */
1289
+ private _runInitialize;
1290
+ /** Reset shared state after a failed `_runInitialize`. */
1291
+ private _cleanupAfterFailedInit;
1292
+ /** Resolve embedding: explicit config > stored DB key > local default. */
1293
+ private _resolveEmbedding;
1294
+ /**
1295
+ * Get or create a per-repo SQLiteAdapter for namespaced plugins.
1296
+ * Non-namespaced plugins use the root DB.
1297
+ * DB path: `.brainbank/<repoName>.db` (e.g., `servicehub-backend.db`).
1298
+ */
1299
+ private _getOrCreatePluginDb;
1300
+ /** Build a per-plugin `PluginContext` with appropriate DB and HNSW scoping. */
1301
+ private _buildPluginContext;
1302
+ /**
1303
+ * Reload a single HNSW index by name.
1304
+ * Discovers the vector table via ReembeddablePlugin capability.
1305
+ * KV is handled directly since it's core-owned.
1306
+ *
1307
+ * The `name` comes from `index_state` and equals the plugin's `mod.name`
1308
+ * (e.g. `code:backend`, `git`, `docs`). This matches the key used in
1309
+ * `getOrCreateSharedHnsw()` during initialization.
1310
+ */
1311
+ private _reloadIndex;
1312
+ /** Guard: throw descriptive error if not initialized. */
251
1313
  private _requireInit;
252
- /** Get the docs indexer as CollectionPlugin with init + type check. */
253
- private _docsPlugin;
254
1314
  }
255
1315
 
1316
+ /**
1317
+ * BrainBank — Constants
1318
+ *
1319
+ * Core-only constants. Plugin names are NOT defined here — they belong
1320
+ * to their respective packages. Only keys owned by the core live here.
1321
+ */
1322
+ /** HNSW index key for KV collections (core-owned). */
1323
+ declare const HNSW: {
1324
+ readonly KV: "kv";
1325
+ };
1326
+ type HnswKey = typeof HNSW[keyof typeof HNSW];
1327
+
256
1328
  /**
257
1329
  * BrainBank — Local Embedding Provider
258
1330
  *
@@ -425,22 +1497,116 @@ declare class PerplexityContextEmbedding implements EmbeddingProvider {
425
1497
  }
426
1498
 
427
1499
  /**
428
- * BrainBank — Math Utilities
1500
+ * BrainBank — Embedding Worker Proxy
429
1501
  *
430
- * Pure vector math functions for similarity calculations.
431
- * No dependencies works on Float32Array directly.
1502
+ * Drop-in replacement for any EmbeddingProvider that offloads
1503
+ * embedding computation to a dedicated worker thread.
1504
+ * The main thread's event loop stays free for serving searches.
1505
+ *
1506
+ * Usage:
1507
+ * const proxy = new EmbeddingWorkerProxy('local', { model: '...' });
1508
+ * await proxy.ready();
1509
+ * const vec = await proxy.embed('hello');
432
1510
  */
1511
+
1512
+ declare class EmbeddingWorkerProxy implements EmbeddingProvider {
1513
+ private _worker;
1514
+ private _nextId;
1515
+ private _pending;
1516
+ private _ready;
1517
+ private _dims;
1518
+ /** Embedding dimensions (available after `ready()` resolves). */
1519
+ get dims(): number;
1520
+ constructor(providerType: string, providerOptions?: Record<string, unknown>);
1521
+ /** Wait for the worker to be ready (provider loaded). */
1522
+ ready(): Promise<void>;
1523
+ /** Send a request to the worker and wait for the response. */
1524
+ private _send;
1525
+ /** Embed a single text string. */
1526
+ embed(text: string): Promise<Float32Array>;
1527
+ /** Embed multiple texts in a batch. */
1528
+ embedBatch(texts: string[]): Promise<Float32Array[]>;
1529
+ /** Terminate the worker. */
1530
+ close(): Promise<void>;
1531
+ }
1532
+
433
1533
  /**
434
- * Cosine similarity between two vectors.
435
- * Assumes vectors are already normalized (unit length).
436
- * Returns value between -1.0 and 1.0.
1534
+ * HttpServer Lightweight JSON API for BrainBank.
1535
+ *
1536
+ * Exposes BrainBank operations over HTTP so CLI commands can delegate
1537
+ * to a running server instead of cold-loading models each time.
1538
+ *
1539
+ * Routes:
1540
+ * POST /context → brain.getContext()
1541
+ * POST /index → brain.index()
1542
+ * GET /health → { ok, pid, uptime, port }
1543
+ *
1544
+ * Multi-repo: a single server handles all repos via WorkspacePool.
1545
+ * Each request includes a `repo` field to select the workspace.
437
1546
  */
438
- declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
1547
+
1548
+ interface HttpServerOptions {
1549
+ port?: number;
1550
+ /** Factory to create a BrainBank instance for a given repo path. */
1551
+ factory: (repoPath: string) => Promise<BrainBank>;
1552
+ /** Default repo path when request doesn't specify one. */
1553
+ defaultRepo?: string;
1554
+ /** Called when an error occurs during pool operations. */
1555
+ onError?: (repo: string, err: unknown) => void;
1556
+ /** Called on server lifecycle events. */
1557
+ onLog?: (msg: string) => void;
1558
+ }
1559
+ declare class HttpServer {
1560
+ private _server;
1561
+ private _pool;
1562
+ private _port;
1563
+ private _defaultRepo;
1564
+ private _startTime;
1565
+ private _log;
1566
+ constructor(options: HttpServerOptions);
1567
+ /** Start listening. Writes PID file for daemon detection. */
1568
+ start(): Promise<void>;
1569
+ /** Stop the server and clean up. */
1570
+ close(): void;
1571
+ get port(): number;
1572
+ private _handleRequest;
1573
+ private _handleHealth;
1574
+ private _handleContext;
1575
+ private _handleIndex;
1576
+ private _readBody;
1577
+ }
1578
+
439
1579
  /**
440
- * L2-normalize a vector to unit length.
441
- * Returns a new Float32Array.
1580
+ * Daemon PID file management for the BrainBank HTTP server.
1581
+ *
1582
+ * PID file: ~/.cache/brainbank/server.pid
1583
+ * Format: JSON { pid: number, port: number }
1584
+ *
1585
+ * Used by:
1586
+ * - `brainbank daemon` to write PID on start
1587
+ * - `brainbank daemon stop` to find and kill the daemon
1588
+ * - `brainbank status` to report server state
1589
+ * - CLI commands to detect running server for delegation
442
1590
  */
443
- declare function normalize(vec: Float32Array): Float32Array;
1591
+ declare const DEFAULT_PORT = 8181;
1592
+ interface PidInfo {
1593
+ pid: number;
1594
+ port: number;
1595
+ }
1596
+ /** Write the PID file after server starts. */
1597
+ declare function writePid(pid: number, port: number): void;
1598
+ /** Read PID info from file. Returns null if missing or malformed. */
1599
+ declare function readPid(): PidInfo | null;
1600
+ /** Remove the PID file. */
1601
+ declare function removePid(): void;
1602
+ /**
1603
+ * Check if the server process is still alive.
1604
+ * Uses `kill(pid, 0)` which doesn't actually send a signal —
1605
+ * it just checks whether the process exists.
1606
+ */
1607
+ declare function isServerRunning(): PidInfo | null;
1608
+ /** Get the server URL if running, or null. */
1609
+ declare function getServerUrl(): string | null;
444
1610
 
445
1611
  /**
446
1612
  * BrainBank — Qwen3 Local Reranker
@@ -463,30 +1629,120 @@ interface Qwen3RerankerOptions {
463
1629
  /** Context size for ranking. Default: 2048 */
464
1630
  contextSize?: number;
465
1631
  }
466
- declare class Qwen3Reranker implements Reranker {
467
- private readonly _modelUri;
468
- private readonly _cacheDir;
469
- private readonly _contextSize;
470
- private _model;
471
- private _context;
472
- private _loadPromise;
473
- constructor(options?: Qwen3RerankerOptions);
474
- /**
475
- * Lazy-load the reranker model and create a ranking context.
476
- * Model is auto-downloaded from HuggingFace on first use.
477
- */
478
- private _ensureLoaded;
479
- /**
480
- * Score each document's relevance to the query.
481
- * Returns scores (0.0 - 1.0) in same order as input documents.
482
- *
483
- * Deduplicates identical documents to avoid redundant computation.
484
- */
485
- rank(query: string, documents: string[]): Promise<number[]>;
486
- /** Release model resources. */
1632
+ declare class Qwen3Reranker implements Reranker {
1633
+ private readonly _modelUri;
1634
+ private readonly _cacheDir;
1635
+ private readonly _contextSize;
1636
+ private _model;
1637
+ private _context;
1638
+ private _loadPromise;
1639
+ constructor(options?: Qwen3RerankerOptions);
1640
+ /**
1641
+ * Lazy-load the reranker model and create a ranking context.
1642
+ * Model is auto-downloaded from HuggingFace on first use.
1643
+ */
1644
+ private _ensureLoaded;
1645
+ /**
1646
+ * Score each document's relevance to the query.
1647
+ * Returns scores (0.0 - 1.0) in same order as input documents.
1648
+ *
1649
+ * Deduplicates identical documents to avoid redundant computation.
1650
+ */
1651
+ rank(query: string, documents: string[]): Promise<number[]>;
1652
+ /** Release model resources. */
1653
+ close(): Promise<void>;
1654
+ }
1655
+
1656
+ /**
1657
+ * BrainBank — Haiku Pruner
1658
+ *
1659
+ * LLM-based noise filter using Anthropic's Haiku 4.5 model.
1660
+ * Binary classification: for each search result, Haiku decides
1661
+ * "relevant" or "noise" based on filePath, metadata, and full
1662
+ * file content (capped at ~8K chars per item by prune.ts).
1663
+ *
1664
+ * Latency: ~300-600ms.
1665
+ */
1666
+
1667
+ interface HaikuPrunerOptions {
1668
+ /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */
1669
+ apiKey?: string;
1670
+ /** Model to use. Default: claude-haiku-4-5-20251001 */
1671
+ model?: string;
1672
+ }
1673
+ declare class HaikuPruner implements Pruner {
1674
+ private readonly _apiKey;
1675
+ private readonly _model;
1676
+ constructor(options?: HaikuPrunerOptions);
1677
+ prune(query: string, items: PrunerItem[]): Promise<number[]>;
1678
+ close(): Promise<void>;
1679
+ }
1680
+
1681
+ /**
1682
+ * BrainBank — Prune Utility
1683
+ *
1684
+ * Bridges SearchResult[] → Pruner.prune() → filtered SearchResult[].
1685
+ * Converts results to lightweight PrunerItems with full content previews,
1686
+ * calls the pruner, and filters out dropped results.
1687
+ *
1688
+ * Content strategy: send the FULL chunk content to the pruner so it can
1689
+ * make informed decisions. A per-item character cap prevents cost blowups
1690
+ * on monster files — when truncated, the middle is kept (imports + core
1691
+ * logic) rather than just the top.
1692
+ */
1693
+
1694
+ /** Run the pruner on search results. Returns results in the order the pruner chose. */
1695
+ declare function pruneResults(query: string, results: SearchResult[], pruner: Pruner): Promise<SearchResult[]>;
1696
+
1697
+ /**
1698
+ * BrainBank — Haiku Expander
1699
+ *
1700
+ * LLM-powered context expansion using Anthropic's Haiku 4.5 model.
1701
+ * After search + pruning, reviews a manifest of available chunks
1702
+ * and requests additional IDs to include.
1703
+ *
1704
+ * Flow:
1705
+ * 1. Receives lightweight manifest (~20 chars per chunk)
1706
+ * 2. Haiku selects additional chunk IDs (just numbers, fast)
1707
+ * 3. Caller fetches those chunks from DB and splices into results
1708
+ *
1709
+ * Designed for minimal token usage:
1710
+ * - Input: ~2,000-3,000 tokens (manifest)
1711
+ * - Output: ~50-100 tokens (ID array)
1712
+ * - Cost: ~$0.001 per call
1713
+ * - Latency: ~300-600ms
1714
+ *
1715
+ * Fail-open: any error returns empty array (no expansion).
1716
+ */
1717
+
1718
+ interface HaikuExpanderOptions {
1719
+ /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */
1720
+ apiKey?: string;
1721
+ /** Model to use. Default: claude-haiku-4-5-20251001 */
1722
+ model?: string;
1723
+ }
1724
+ declare class HaikuExpander implements Expander {
1725
+ private readonly _apiKey;
1726
+ private readonly _model;
1727
+ constructor(options?: HaikuExpanderOptions);
1728
+ expand(query: string, currentIds: number[], manifest: ExpanderManifestItem[]): Promise<ExpanderResult>;
1729
+ /** Parse Haiku response — handles both `{ ids, note }` and bare `[...]` formats. */
1730
+ private _parseResponse;
487
1731
  close(): Promise<void>;
488
1732
  }
489
1733
 
1734
+ /**
1735
+ * BrainBank — Provider Key
1736
+ *
1737
+ * Infers a stable key from an existing EmbeddingProvider instance.
1738
+ * Lives in lib/ (Layer 0) to avoid db/ → providers/ dependency.
1739
+ */
1740
+
1741
+ /** Known embedding provider keys. */
1742
+ type EmbeddingKey = 'local' | 'openai' | 'perplexity' | 'perplexity-context';
1743
+ /** Infer a stable key from an existing provider instance. */
1744
+ declare function providerKey(p: EmbeddingProvider): EmbeddingKey;
1745
+
490
1746
  /**
491
1747
  * BrainBank — Embedding Provider Resolver
492
1748
  *
@@ -494,12 +1750,8 @@ declare class Qwen3Reranker implements Reranker {
494
1750
  * Used by the Initializer to auto-resolve from DB config.
495
1751
  */
496
1752
 
497
- /** Known embedding provider keys. */
498
- type EmbeddingKey = 'local' | 'openai' | 'perplexity' | 'perplexity-context';
499
1753
  /** Resolve an EmbeddingProvider from a key string. Lazy-loads the provider module. */
500
1754
  declare function resolveEmbedding(key: string): Promise<EmbeddingProvider>;
501
- /** Infer a stable key from an existing provider instance. */
502
- declare function providerKey(p: EmbeddingProvider): EmbeddingKey;
503
1755
 
504
1756
  declare const DEFAULTS: ResolvedConfig;
505
1757
  /**
@@ -526,196 +1778,6 @@ declare function resolveConfig(partial?: BrainBankConfig): ResolvedConfig;
526
1778
  */
527
1779
  declare function searchMMR(index: VectorIndex, query: Float32Array, vectorCache: Map<number, Float32Array>, k: number, lambda?: number): SearchHit[];
528
1780
 
529
- /**
530
- * BrainBank — Tree-Sitter Code Chunker
531
- *
532
- * AST-aware code splitting using native tree-sitter bindings.
533
- * Extracts semantic blocks (functions, classes, methods, interfaces)
534
- * from the AST. Falls back to sliding window for unsupported languages.
535
- */
536
-
537
- interface ChunkerConfig {
538
- /** Max lines per chunk. Default: 80 */
539
- maxLines?: number;
540
- /** Min lines for a detected block to be a chunk. Default: 3 */
541
- minLines?: number;
542
- /** Overlap between adjacent generic chunks. Default: 5 */
543
- overlap?: number;
544
- }
545
- declare class CodeChunker {
546
- private MAX;
547
- private MIN;
548
- private OVERLAP;
549
- private _parser;
550
- private _langCache;
551
- constructor(config?: ChunkerConfig);
552
- /** Lazy-init tree-sitter parser. */
553
- private _ensureParser;
554
- /** Load a language grammar (cached). Throws if grammar package is not installed. */
555
- private _loadGrammar;
556
- /**
557
- * Split file content into semantic chunks using tree-sitter AST.
558
- * Throws if the language has a grammar but the package isn't installed.
559
- * Falls back to sliding window only for truly unsupported languages.
560
- */
561
- chunk(filePath: string, content: string, language: string): Promise<CodeChunk[]>;
562
- /** Walk AST and extract top-level semantic blocks. */
563
- private _extractChunks;
564
- /** Classify and process a single AST node. */
565
- private _processNode;
566
- /** Check which category a node type belongs to. */
567
- private _categorize;
568
- /** Process a matched declaration: class → split by methods, else → chunk directly. */
569
- private _processDeclaration;
570
- /** Split a large class into individual method chunks. */
571
- private _splitClassIntoMethods;
572
- /** Find the class body node. */
573
- private _findClassBody;
574
- /** Extract name from an AST node. */
575
- private _extractName;
576
- /** Map category to chunk type. */
577
- private _toChunkType;
578
- /** Add a node as a chunk, avoiding duplicates. */
579
- private _addChunk;
580
- private _chunkGeneric;
581
- /** Split a large block into overlapping sub-chunks. */
582
- private _splitLargeBlock;
583
- }
584
-
585
- /**
586
- * BrainBank — Code Indexer
587
- *
588
- * Walks a repository, chunks source files semantically,
589
- * embeds each chunk, and stores in SQLite + HNSW.
590
- * Incremental: only re-indexes files that changed (by content hash).
591
- */
592
-
593
- interface CodeWalkerDeps {
594
- db: Database;
595
- hnsw: HNSWIndex;
596
- vectorCache: Map<number, Float32Array>;
597
- embedding: EmbeddingProvider;
598
- }
599
- interface CodeIndexOptions {
600
- forceReindex?: boolean;
601
- onProgress?: ProgressCallback;
602
- }
603
- declare class CodeWalker {
604
- private _chunker;
605
- private _deps;
606
- private _repoPath;
607
- private _maxFileSize;
608
- constructor(repoPath: string, deps: CodeWalkerDeps, maxFileSize?: number);
609
- /** Index all supported files. Skips unchanged files (same content hash). */
610
- index(options?: CodeIndexOptions): Promise<IndexResult>;
611
- /** Remove old chunks and their HNSW vectors for a file. */
612
- private _removeOldChunks;
613
- /** Chunk, embed, and store a single file. Returns chunk count. */
614
- private _indexFile;
615
- private _walkRepo;
616
- private _hash;
617
- }
618
-
619
- /**
620
- * BrainBank — Git Indexer
621
- *
622
- * Reads git history, embeds commit messages + diffs,
623
- * and computes file co-edit relationships.
624
- * Incremental: only processes new commits.
625
- */
626
-
627
- interface GitIndexerDeps {
628
- db: Database;
629
- hnsw: HNSWIndex;
630
- vectorCache: Map<number, Float32Array>;
631
- embedding: EmbeddingProvider;
632
- }
633
- interface GitIndexOptions {
634
- depth?: number;
635
- onProgress?: ProgressCallback;
636
- }
637
- declare class GitIndexer {
638
- private _deps;
639
- private _repoPath;
640
- private _maxDiffBytes;
641
- constructor(repoPath: string, deps: GitIndexerDeps, maxDiffBytes?: number);
642
- /**
643
- * Index git history.
644
- * Only processes commits not already in the database.
645
- */
646
- index(options?: GitIndexOptions): Promise<IndexResult>;
647
- /** Initialize simple-git. Returns null if git is unavailable. */
648
- private _initGit;
649
- /** Prepare all SQL statements (hoisted outside loops). */
650
- private _prepareStatements;
651
- /** Phase 1: Collect commit data from git (async git calls). */
652
- private _collectCommits;
653
- /** Extract diff, stat, and text from a single commit. */
654
- private _parseCommit;
655
- /** Phase 3: Insert commits + vectors in a single transaction. */
656
- private _insertCommits;
657
- /** Phase 4: Update HNSW index and compute co-edits. */
658
- private _updateHnsw;
659
- /** Compute which files tend to be edited together. */
660
- private _computeCoEdits;
661
- /** Query commit_files in chunks to stay under SQLite's 999-variable limit. */
662
- private _queryCommitFiles;
663
- /** Group file paths by commit ID. */
664
- private _groupFilesByCommit;
665
- }
666
-
667
- /**
668
- * BrainBank — Document Indexer
669
- *
670
- * Indexes generic document collections (markdown, text, etc.)
671
- * with heading-aware smart chunking, inspired by qmd.
672
- *
673
- * const indexer = new DocsIndexer(db, embedding, hnsw, vecCache);
674
- * await indexer.indexCollection('notes', '/path/to/notes', '**\/*.md');
675
- */
676
-
677
- declare class DocsIndexer {
678
- private _db;
679
- private _embedding;
680
- private _hnsw;
681
- private _vecCache;
682
- constructor(_db: Database, _embedding: EmbeddingProvider, _hnsw: HNSWIndex, _vecCache: Map<number, Float32Array>);
683
- /**
684
- * Index all documents in a collection.
685
- * Incremental — skips unchanged files (by content hash).
686
- */
687
- indexCollection(collection: string, dirPath: string, pattern?: string, options?: {
688
- ignore?: string[];
689
- onProgress?: (file: string, current: number, total: number) => void;
690
- }): Promise<{
691
- indexed: number;
692
- skipped: number;
693
- chunks: number;
694
- }>;
695
- /** Walk directory tree and collect matching files. */
696
- private _walkFiles;
697
- /** Check if a file matches any ignore patterns. */
698
- private _isIgnoredFile;
699
- /** Check if all chunks for a file match the current hash and have vectors. */
700
- private _isUnchanged;
701
- /** Remove old chunks and their HNSW vectors for a file. */
702
- private _removeOldChunks;
703
- /** Index a single file: chunk, embed, store in DB + HNSW. */
704
- private _indexFile;
705
- /** Remove all indexed data for a collection. */
706
- removeCollection(collection: string): void;
707
- /** Split document into chunks at natural markdown boundaries. */
708
- private _smartChunk;
709
- /** Handle the last chunk: merge if too small, otherwise push. */
710
- private _flushRemainder;
711
- /** Find the best break position within the target window. */
712
- private _findBestBreak;
713
- /** Find all potential break points in the document with scores. */
714
- private _findBreakPoints;
715
- /** Extract document title from first heading or filename. */
716
- private _extractTitle;
717
- }
718
-
719
1781
  /**
720
1782
  * BrainBank — Language Registry
721
1783
  *
@@ -728,281 +1790,206 @@ declare const IGNORE_DIRS: Set<string>;
728
1790
  declare function isSupported(filePath: string): boolean;
729
1791
  /** Get the language name for a file. Returns undefined if not supported. */
730
1792
  declare function getLanguage(filePath: string): string | undefined;
1793
+ /** Check if a directory name should be ignored. */
1794
+ declare function isIgnoredDir(dirName: string): boolean;
1795
+ /** Check if a filename should be ignored. */
1796
+ declare function isIgnoredFile(fileName: string): boolean;
731
1797
 
732
1798
  /**
733
- * BrainBank — Pattern Store (Agent Memory)
1799
+ * BrainBank — Math Utilities
734
1800
  *
735
- * Stores what the agent learned from past tasks.
736
- * Each pattern records task, approach, and success rate.
737
- * Searchable by semantic similarity via HNSW.
1801
+ * Pure vector math functions for similarity calculations.
1802
+ * No dependencies works on Float32Array directly.
738
1803
  */
739
-
740
- interface PatternStoreDeps {
741
- db: Database;
742
- hnsw: HNSWIndex;
743
- vectorCache: Map<number, Float32Array>;
744
- embedding: EmbeddingProvider;
745
- }
746
- declare class PatternStore {
747
- private _deps;
748
- constructor(deps: PatternStoreDeps);
749
- /**
750
- * Store a learned pattern.
751
- * Returns the pattern ID.
752
- */
753
- learn(pattern: LearningPattern): Promise<number>;
754
- /**
755
- * Search for similar successful patterns.
756
- * Filters by minimum success rate.
757
- */
758
- search(query: string, k?: number, minSuccess?: number): Promise<(LearningPattern & {
759
- score: number;
760
- })[]>;
761
- /**
762
- * Get all patterns for a specific task type.
763
- */
764
- getByTaskType(taskType: string, limit?: number): LearningPattern[];
765
- /** Total number of stored patterns. */
766
- get count(): number;
767
- }
1804
+ /**
1805
+ * Cosine similarity between two vectors.
1806
+ * Assumes vectors are already normalized (unit length).
1807
+ * Returns value between -1.0 and 1.0.
1808
+ */
1809
+ declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
1810
+ /**
1811
+ * L2-normalize a vector to unit length.
1812
+ * Returns a new Float32Array.
1813
+ */
1814
+ declare function normalize(vec: Float32Array): Float32Array;
1815
+ /**
1816
+ * Convert a Float32Array to a Buffer for SQLite storage.
1817
+ * Handles views with non-zero byteOffset (e.g. from batched embedding output).
1818
+ * Using Buffer.from(vec.buffer) directly is WRONG for views — it copies the entire parent buffer.
1819
+ */
1820
+ declare function vecToBuffer(vec: Float32Array): Buffer;
768
1821
 
769
1822
  /**
770
- * BrainBank — Consolidator
1823
+ * BrainBank — KV Service
771
1824
  *
772
- * Maintenance operations for the agent memory:
773
- * - prune: remove old failed patterns
774
- * - dedup: merge near-duplicate patterns (cosine > 0.95)
775
- * - consolidate: run both
1825
+ * Owns the shared HNSW index and vector cache for KV collections.
1826
+ * Provides collection creation, listing, and deletion.
1827
+ * Extracted from BrainBank to separate infrastructure from facade.
776
1828
  */
777
1829
 
778
- declare class Consolidator {
1830
+ declare class KVService {
779
1831
  private _db;
780
- private _vectorCache;
781
- constructor(_db: Database, _vectorCache: Map<number, Float32Array>);
782
- /**
783
- * Remove old failed patterns.
784
- * Criteria: success_rate < 0.3 AND created > 90 days ago.
785
- */
786
- prune(maxAgeDays?: number, minSuccess?: number): number;
787
- /**
788
- * Merge near-duplicate patterns.
789
- * Keeps the one with higher success_rate.
790
- * Threshold: cosine similarity > 0.95.
791
- */
792
- dedup(threshold?: number): number;
793
- /**
794
- * Run full consolidation: prune + dedup.
795
- */
796
- consolidate(): {
797
- pruned: number;
798
- deduped: number;
799
- };
1832
+ private _embedding;
1833
+ private _hnsw;
1834
+ private _vecs;
1835
+ private _reranker?;
1836
+ private _collections;
1837
+ constructor(_db: DatabaseAdapter, _embedding: EmbeddingProvider, _hnsw: HNSWIndex, _vecs: Map<number, Float32Array>, _reranker?: Reranker | undefined);
1838
+ /** Get or create a named collection. */
1839
+ collection(name: string): Collection;
1840
+ /** List all collection names that have data. */
1841
+ listNames(): string[];
1842
+ /** Delete a collection's data and evict from cache. Removes vectors from HNSW to prevent ghost entries. */
1843
+ delete(name: string): void;
1844
+ /** Access the shared HNSW index (used by reembed). */
1845
+ get hnsw(): HNSWIndex;
1846
+ /** Access the shared vector cache. @internal */
1847
+ get vecs(): Map<number, Float32Array>;
1848
+ /** Clear all cached collections and vectors. */
1849
+ clear(): void;
800
1850
  }
801
1851
 
802
1852
  /**
803
- * BrainBank — Note Memory Store
1853
+ * BrainBank — Plugin Registry
804
1854
  *
805
- * Stores structured note digests for long-term agent memory.
806
- * Each digest captures decisions, files changed, patterns, and open questions.
807
- * Supports vector + BM25 hybrid retrieval via HNSW + FTS5.
1855
+ * Manages registration and lookup of plugins.
1856
+ * Extracted from BrainBank so the facade stays focused on orchestration.
808
1857
  *
809
- * Memory tiers:
810
- * - "short" (default): Full digest, last ~20 notes
811
- * - "long": Compressed to patterns + decisions only
1858
+ * Responsibilities:
1859
+ * - Store plugins by name
1860
+ * - Type-prefix matching ('code' finds 'code:frontend', 'code:backend')
1861
+ * - Alias resolution (currently none; add here if needed)
1862
+ * - Consistent error messages on missing plugins
812
1863
  */
813
1864
 
814
- interface NoteDigest {
815
- title: string;
816
- summary: string;
817
- decisions?: string[];
818
- filesChanged?: string[];
819
- patterns?: string[];
820
- openQuestions?: string[];
821
- tags?: string[];
822
- }
823
- interface StoredNote extends NoteDigest {
824
- id: number;
825
- tier: 'short' | 'long';
826
- createdAt: number;
827
- score?: number;
828
- }
829
- interface RecallOptions {
830
- /** Max results. Default: 5 */
831
- k?: number;
832
- /** Search mode. Default: 'hybrid' */
833
- mode?: 'hybrid' | 'vector' | 'keyword';
834
- /** Minimum score threshold. Default: 0.15 */
835
- minScore?: number;
836
- /** Filter by tier. Default: all */
837
- tier?: 'short' | 'long';
838
- }
839
- declare class NoteStore {
840
- private _db;
841
- private _embedding;
842
- private _hnsw;
843
- private _vecs;
844
- constructor(db: Database, embedding: EmbeddingProvider, hnsw: HNSWIndex, vecs: Map<number, Float32Array>);
845
- /**
846
- * Store a note digest.
847
- * Embeds title + summary for vector search, auto-indexed in FTS5.
848
- */
849
- remember(digest: NoteDigest): Promise<number>;
850
- /**
851
- * Recall relevant notes.
852
- * Supports vector, keyword, or hybrid (default) retrieval.
853
- */
854
- recall(query: string, options?: RecallOptions): Promise<StoredNote[]>;
1865
+ declare class PluginRegistry {
1866
+ private _map;
1867
+ /** Store a plugin. Duplicate names silently overwrite. */
1868
+ register(plugin: Plugin): void;
855
1869
  /**
856
- * List recent notes.
1870
+ * Check whether a plugin is registered.
1871
+ * Supports type-prefix matching: `has('code')` returns true if
1872
+ * 'code', 'code:frontend', or 'code:backend' is registered.
857
1873
  */
858
- list(limit?: number, tier?: 'short' | 'long'): StoredNote[];
1874
+ has(name: string): boolean;
859
1875
  /**
860
- * Get total count of notes.
1876
+ * Get a plugin by name. Throws a descriptive error if not found.
1877
+ *
1878
+ * Resolution order:
1879
+ * 1. Alias map (currently empty)
1880
+ * 2. Exact match
1881
+ * 3. First type-prefix match ('code' → 'code:frontend')
861
1882
  */
862
- count(): {
863
- total: number;
864
- short: number;
865
- long: number;
866
- };
1883
+ get<T extends Plugin = Plugin>(name: string): T;
867
1884
  /**
868
- * Consolidate old short-term notes into long-term.
869
- * Keeps the most recent `keepRecent` as short-term, compresses the rest.
1885
+ * Return every plugin whose name equals `type` or starts with `type + ':'`.
1886
+ * Example: allByType('code') [code, code:frontend, code:backend]
870
1887
  */
871
- consolidate(keepRecent?: number): {
872
- promoted: number;
873
- };
874
- private _searchVector;
875
- private _searchBM25;
876
- private _rowToNote;
877
- }
878
-
879
- /**
880
- * BrainBank — Search Types
881
- *
882
- * Shared interface for all search strategies.
883
- * Implement SearchStrategy to add a new search backend.
884
- */
885
-
886
- /** Any search implementation follows this shape. */
887
- interface SearchStrategy {
888
- search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
889
- /** Rebuild internal indices (e.g. FTS5). Optional. */
890
- rebuild?(): void;
891
- }
892
- interface SearchOptions {
893
- /** Max code results. Default: 6 */
894
- codeK?: number;
895
- /** Max git results. Default: 5 */
896
- gitK?: number;
897
- /** Max pattern results. Default: 4 */
898
- patternK?: number;
899
- /** Minimum similarity score. Default: 0.25 */
900
- minScore?: number;
901
- /** Use MMR for diversity. Default: true */
902
- useMMR?: boolean;
903
- /** MMR lambda. Default: 0.7 */
904
- mmrLambda?: number;
905
- }
906
-
907
- /**
908
- * BrainBank — Co-Edit Analyzer
909
- *
910
- * Suggests files that historically change together.
911
- * Based on git commit co-occurrence analysis.
912
- */
913
-
914
- declare class CoEditAnalyzer {
915
- private _db;
916
- constructor(_db: Database);
1888
+ allByType(type: string): Plugin[];
1889
+ /** Return the first plugin that matches the type prefix, or undefined. */
1890
+ firstByType(type: string): Plugin | undefined;
1891
+ /** All registered plugin names (insertion order). */
1892
+ get names(): string[];
1893
+ /** All registered plugin instances (insertion order). */
1894
+ get all(): Plugin[];
917
1895
  /**
918
- * Get files that frequently change alongside the given file.
919
- * Returns sorted by co-edit count (highest first).
1896
+ * Underlying Map.
1897
+ * Prefer `all`, `allByType`, or `firstByType` everywhere else.
920
1898
  */
921
- suggest(filePath: string, limit?: number): CoEditSuggestion[];
1899
+ get raw(): Map<string, Plugin>;
1900
+ /** Remove all registered plugins. Called by BrainBank.close(). */
1901
+ clear(): void;
922
1902
  }
923
1903
 
924
1904
  /**
925
1905
  * BrainBank — Context Builder
926
1906
  *
927
- * Builds a formatted markdown context block from search results.
928
- * Ready for injection into an LLM system prompt.
929
- * Groups code by file, includes git history and learned patterns.
1907
+ * Orchestrates the context-building pipeline:
1908
+ * 1. Vector search (primary)
1909
+ * 2. Path scoping (filter)
1910
+ * 3. LLM noise pruning (optional)
1911
+ * 4. Session dedup (filter)
1912
+ * 5. LLM context expansion (optional — expander field)
1913
+ * 6. Plugin formatters (output)
1914
+ *
1915
+ * All search post-processing lives in `bm25-boost.ts`.
1916
+ * Plugin-agnostic — discovers formatters from ContextFormatterPlugin.
930
1917
  */
931
1918
 
932
1919
  declare class ContextBuilder {
933
1920
  private _search;
934
- private _coEdits?;
935
- constructor(_search: SearchStrategy, _coEdits?: CoEditAnalyzer | undefined);
1921
+ private _registry;
1922
+ private _pruner?;
1923
+ private _embedding?;
1924
+ private _rerankerName?;
1925
+ private _configFields;
1926
+ private _expander?;
1927
+ constructor(_search: SearchStrategy | undefined, _registry: PluginRegistry, _pruner?: Pruner | undefined, _embedding?: EmbeddingProvider | undefined, _rerankerName?: string | undefined, _configFields?: Record<string, unknown>, _expander?: Expander | undefined);
1928
+ /** Set config-level context field defaults (from config.json "context" section). */
1929
+ set configFields(fields: Record<string, unknown>);
1930
+ /** Set the expander instance. */
1931
+ set expander(expander: Expander | undefined);
936
1932
  /** Build a full context block for a task. Returns markdown for system prompt. */
937
1933
  build(task: string, options?: ContextOptions): Promise<string>;
938
- /** Format code search results grouped by file. */
939
- private _formatCodeResults;
940
- /** Format git commit results with diff snippets. */
941
- private _formatGitResults;
942
- /** Format co-edit suggestions for affected files. */
943
- private _formatCoEdits;
944
- /** Format memory pattern results. */
945
- private _formatPatternResults;
1934
+ /** Invoke ContextFormatterPlugins. Multi-repo: each plugin formats its own results. */
1935
+ private _appendFormatterResults;
1936
+ /**
1937
+ * Resolve context fields: plugin defaults ← config.json ← per-query.
1938
+ * Returns a flat Record with the final value for each field.
1939
+ */
1940
+ private _resolveFields;
1941
+ /**
1942
+ * Run LLM expansion: build manifest of candidate chunks from files
1943
+ * NOT already in search results, call expander, resolve selected IDs.
1944
+ */
1945
+ private _expand;
1946
+ /** Detect the multi-repo prefix from existing results (e.g. 'servicehub-frontend'). */
1947
+ private _detectRepoPrefix;
1948
+ /** Collect results from SearchablePlugins that don't have their own formatter. */
1949
+ private _appendSearchableResults;
946
1950
  }
947
1951
 
948
1952
  /**
949
- * BrainBank — Vector Search Strategy
1953
+ * BrainBank — Composite Vector Search
950
1954
  *
951
- * Searches across code, git, and memory pattern HNSW indices.
952
- * Returns typed results sorted by relevance.
1955
+ * Generic orchestrator for domain-specific vector searches.
1956
+ * Embeds the query once, delegates to registered DomainVectorSearch strategies.
1957
+ * Uses round-robin interleaving when multiple strategies exist to ensure
1958
+ * balanced representation across repos/domains.
1959
+ * Plugin-agnostic — strategies are discovered at wiring time.
953
1960
  */
954
1961
 
955
- interface VectorSearchConfig {
956
- db: Database;
957
- codeHnsw?: HNSWIndex;
958
- gitHnsw?: HNSWIndex;
959
- patternHnsw?: HNSWIndex;
960
- codeVecs: Map<number, Float32Array>;
961
- gitVecs: Map<number, Float32Array>;
962
- patternVecs: Map<number, Float32Array>;
1962
+ interface CompositeVectorConfig {
1963
+ strategies: Map<string, DomainVectorSearch>;
963
1964
  embedding: EmbeddingProvider;
964
- reranker?: Reranker;
1965
+ /** Default K values per strategy name. Strategies not listed default to 0. */
1966
+ defaults?: Record<string, number>;
965
1967
  }
966
- declare class VectorSearch implements SearchStrategy {
967
- private _config;
968
- constructor(config: VectorSearchConfig);
969
- /** Search across all indices. Returns combined results sorted by score. */
1968
+ declare class CompositeVectorSearch implements SearchStrategy {
1969
+ private _c;
1970
+ /** Default K when no source override is provided. */
1971
+ private static readonly DEFAULT_K;
1972
+ constructor(_c: CompositeVectorConfig);
1973
+ /** Search across all registered domain strategies with score-based merge. */
970
1974
  search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
971
- /** Vector search across code chunks. */
972
- private _searchCode;
973
- /** Vector search across git commits. */
974
- private _searchGit;
975
- /** Vector search across memory patterns. */
976
- private _searchPatterns;
977
1975
  }
978
1976
 
979
1977
  /**
980
- * BrainBank — Keyword Search Strategy
1978
+ * BrainBank — Composite BM25 Search Strategy
981
1979
  *
982
- * Keyword search via SQLite FTS5 with BM25 ranking.
983
- * Searches across code chunks, git commits, and memory patterns.
984
- * Uses Porter stemming + unicode61 tokenizer.
1980
+ * Generic BM25 coordinator that discovers BM25SearchPlugin instances
1981
+ * from the registry and delegates per-source keyword search.
985
1982
  */
986
1983
 
987
- declare class KeywordSearch implements SearchStrategy {
988
- private _db;
989
- constructor(_db: Database);
1984
+ declare class CompositeBM25Search implements SearchStrategy {
1985
+ private _registry;
1986
+ constructor(_registry: PluginRegistry);
990
1987
  /**
991
- * Full-text keyword search across all FTS5 indices.
992
- * Uses BM25 scoring lower scores = better matches.
1988
+ * Run BM25 keyword search across all plugins that implement BM25SearchPlugin.
1989
+ * Each plugin searches its own FTS5 tables.
993
1990
  */
994
1991
  search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
995
- /** FTS5 search across code chunks + file-path fallback. */
996
- private _searchCode;
997
- /** File-path fallback: match filenames via LIKE. */
998
- private _searchCodeByPath;
999
- /** FTS5 search across git commits. */
1000
- private _searchGit;
1001
- /** FTS5 search across memory patterns. */
1002
- private _searchPatterns;
1003
- /** Map a code_chunks row to a CodeResult. */
1004
- private _toCodeResult;
1005
- /** Rebuild the FTS index from scratch. */
1992
+ /** Rebuild FTS5 indices across all BM25 plugins. */
1006
1993
  rebuild(): void;
1007
1994
  }
1008
1995
 
@@ -1028,4 +2015,180 @@ declare class KeywordSearch implements SearchStrategy {
1028
2015
  */
1029
2016
  declare function reciprocalRankFusion(resultSets: SearchResult[][], k?: number, maxResults?: number): SearchResult[];
1030
2017
 
1031
- export { KeywordSearch as BM25Search, BrainBank, BrainBankConfig, CoEditAnalyzer, CoEditSuggestion, CodeChunk, CodeChunker, CodeWalker, Collection, Consolidator, ContextBuilder, ContextOptions, DEFAULTS, DocsIndexer, DocumentCollection, type EmbeddingKey, EmbeddingProvider, GitIndexer, HNSWIndex, IGNORE_DIRS, IndexResult, IndexStats, KeywordSearch, LearningPattern, LocalEmbedding, VectorSearch as MultiIndexSearch, type NoteDigest, NoteStore, OpenAIEmbedding, type OpenAIEmbeddingOptions, PatternStore, PerplexityContextEmbedding, type PerplexityContextEmbeddingOptions, PerplexityEmbedding, type PerplexityEmbeddingOptions, Plugin, ProgressCallback, Qwen3Reranker, type Qwen3RerankerOptions, type RecallOptions, type ReembedOptions, type ReembedResult, Reranker, ResolvedConfig, SUPPORTED_EXTENSIONS, SearchHit, type SearchOptions, SearchResult, type SearchStrategy, StageProgressCallback, type StoredNote, VectorIndex, VectorSearch, type WatchOptions, type Watcher, cosineSimilarity, getLanguage, isSupported, normalize, providerKey, reciprocalRankFusion, resolveConfig, resolveEmbedding, searchMMR };
2018
+ /**
2019
+ * BrainBank — FTS Utilities
2020
+ *
2021
+ * Shared helpers for SQLite FTS5 query sanitization.
2022
+ */
2023
+ /**
2024
+ * Sanitize a user query for FTS5 syntax.
2025
+ * Strips operators that would cause parse errors, splits compound words,
2026
+ * and converts words to implicit AND with exact-match quoting.
2027
+ */
2028
+ declare function sanitizeFTS(query: string): string;
2029
+ /**
2030
+ * Normalize BM25 score from SQLite (negative, lower = better)
2031
+ * to 0.0–1.0 (higher = better) for consistency with vector search.
2032
+ */
2033
+ declare function normalizeBM25(rawScore: number): number;
2034
+ /**
2035
+ * Escape SQL LIKE wildcard characters (`%`, `_`, `\`).
2036
+ * Use with `LIKE ? ESCAPE '\'` in queries.
2037
+ */
2038
+ declare function escapeLike(s: string): string;
2039
+
2040
+ /**
2041
+ * BrainBank — Rerank
2042
+ *
2043
+ * Position-aware score blending between retrieval and reranker.
2044
+ * Pure function — no state.
2045
+ *
2046
+ * Top 1-3: 75% retrieval / 25% reranker (preserves exact matches)
2047
+ * Top 4-10: 60% retrieval / 40% reranker
2048
+ * Top 11+: 40% retrieval / 60% reranker (trust reranker more)
2049
+ */
2050
+
2051
+ /** Re-rank results using position-aware blending. */
2052
+ declare function rerank(query: string, results: SearchResult[], reranker: Reranker): Promise<SearchResult[]>;
2053
+
2054
+ /**
2055
+ * BrainBank — Database Metadata
2056
+ *
2057
+ * Helpers for reading/writing metadata stored in core SQLite tables:
2058
+ *
2059
+ * - **Index State** — cross-process HNSW version tracking.
2060
+ * Processes compare in-memory versions with DB to detect staleness
2061
+ * and trigger hot-reload via `ensureFresh()`.
2062
+ *
2063
+ * - **Embedding Meta** — tracks which embedding provider is stored in
2064
+ * the database. Detects dimension mismatches at startup and updates
2065
+ * metadata after `reembed()`.
2066
+ */
2067
+
2068
+ /**
2069
+ * Increment the version for a given index name.
2070
+ * Sets writer_pid to current process PID.
2071
+ * Uses UPSERT so the row is created on first call.
2072
+ */
2073
+ declare function bumpVersion(db: DatabaseAdapter, name: string): number;
2074
+ /**
2075
+ * Get all index versions as a Map.
2076
+ * Used by `ensureFresh()` to compare against in-memory versions.
2077
+ */
2078
+ declare function getVersions(db: DatabaseAdapter): Map<string, number>;
2079
+ /** Get the version of a single index. Returns 0 if not found. */
2080
+ declare function getVersion(db: DatabaseAdapter, name: string): number;
2081
+
2082
+ /**
2083
+ * BrainBank — Write Lock
2084
+ *
2085
+ * Advisory file lock for cross-process HNSW write exclusion.
2086
+ * Uses `O_CREAT | O_EXCL` for atomic lock creation — works on all OS.
2087
+ * Stale locks (dead PID) are detected and stolen automatically.
2088
+ */
2089
+ /**
2090
+ * Acquire an advisory lock. Blocks with exponential backoff if another
2091
+ * process holds the lock. Steals stale locks from dead processes.
2092
+ *
2093
+ * @throws After MAX_WAIT_MS if the lock cannot be acquired.
2094
+ */
2095
+ declare function acquireLock(lockDir: string, name: string): Promise<void>;
2096
+ /** Release an advisory lock. Safe to call even if not held. */
2097
+ declare function releaseLock(lockDir: string, name: string): void;
2098
+ /**
2099
+ * Execute a function while holding an advisory lock.
2100
+ * Lock is always released, even on error.
2101
+ */
2102
+ declare function withLock<T>(lockDir: string, name: string, fn: () => T | Promise<T>): Promise<T>;
2103
+
2104
+ /**
2105
+ * BrainBank — SQLite Adapter
2106
+ *
2107
+ * Implements `DatabaseAdapter` using `better-sqlite3`.
2108
+ * Drop-in replacement for the old `Database` class.
2109
+ * Handles WAL mode, directory creation, schema init, and transactions.
2110
+ */
2111
+
2112
+ declare class SQLiteAdapter implements DatabaseAdapter {
2113
+ private _db;
2114
+ readonly capabilities: AdapterCapabilities;
2115
+ constructor(dbPath: string);
2116
+ /** Prepare a reusable statement. */
2117
+ prepare<T = unknown>(sql: string): PreparedStatement<T>;
2118
+ /** Execute raw SQL (no results). */
2119
+ exec(sql: string): void;
2120
+ /** Run a function inside a transaction. Auto-commits on success, auto-rollbacks on error. */
2121
+ transaction<T>(fn: () => T): T;
2122
+ /** Run a prepared statement on multiple rows. Wraps in a single transaction. */
2123
+ batch<T extends unknown[]>(sql: string, rows: T[]): void;
2124
+ /** Close the database. */
2125
+ close(): void;
2126
+ /**
2127
+ * Access the underlying `better-sqlite3` Database instance.
2128
+ *
2129
+ * @deprecated Use `DatabaseAdapter` methods instead. This exists
2130
+ * only for gradual migration of plugins that depend on driver internals.
2131
+ */
2132
+ raw<T = unknown>(): T;
2133
+ }
2134
+
2135
+ /**
2136
+ * BrainBank — Brain Context
2137
+ *
2138
+ * Portable input for `createBrain()`. Decouples the factory from
2139
+ * `process.argv` / `process.env` so it can be called from the CLI,
2140
+ * MCP server, tests, or any programmatic consumer.
2141
+ */
2142
+ /** Everything the factory needs to build a BrainBank instance. */
2143
+ interface BrainContext {
2144
+ /** Repository root path. */
2145
+ repoPath: string;
2146
+ /** Environment variable overrides. Falls back to `process.env`. */
2147
+ env?: Record<string, string | undefined>;
2148
+ /** CLI flag overrides (e.g. `{ ignore: 'dist,vendor' }`). */
2149
+ flags?: Record<string, string | undefined>;
2150
+ }
2151
+ /** Build a `BrainContext` from CLI argv + process.env. */
2152
+ declare function contextFromCLI(repoPath?: string): BrainContext;
2153
+
2154
+ /**
2155
+ * BrainBank CLI — Config Loader
2156
+ *
2157
+ * Loads .brainbank/config.json (or .ts/.js/.mjs fallback).
2158
+ * Config priority: CLI flags > config file > defaults.
2159
+ */
2160
+
2161
+ /** Full .brainbank/config.json schema. */
2162
+ interface ProjectConfig {
2163
+ plugins?: string[];
2164
+ embedding?: string;
2165
+ reranker?: string;
2166
+ pruner?: string;
2167
+ maxFileSize?: number;
2168
+ indexers?: Plugin[];
2169
+ brainbank?: Partial<BrainBankConfig>;
2170
+ /** Context field defaults (e.g. { lines: true, callTree: true, symbols: false }). */
2171
+ context?: Record<string, unknown>;
2172
+ /** Per-plugin config sections (e.g. code, git, docs). */
2173
+ [pluginName: string]: unknown;
2174
+ }
2175
+
2176
+ /**
2177
+ * BrainBank CLI — Brain Factory
2178
+ *
2179
+ * Creates a configured BrainBank instance with dynamically loaded plugins,
2180
+ * auto-discovered indexers, and config file support.
2181
+ * Delegates to focused modules in factory/.
2182
+ */
2183
+
2184
+ /** Reset factory caches. Useful for tests. */
2185
+ declare function resetFactoryCache(): void;
2186
+ /**
2187
+ * Create a BrainBank with built-in + discovered + config plugins.
2188
+ *
2189
+ * Accepts either a `BrainContext` (for programmatic use) or an optional
2190
+ * `repoPath` string (for CLI backward compat — builds context from argv).
2191
+ */
2192
+ declare function createBrain(contextOrRepo?: BrainContext | string): Promise<BrainBank>;
2193
+
2194
+ 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 PreparedStatement, type ProgressCallback, type ProjectConfig, type Pruner, type PrunerItem, Qwen3Reranker, type Qwen3RerankerOptions, type ReembedOptions, type ReembedResult, type ReembedTable, type ReembeddablePlugin, type Reranker, 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, rerank, resetFactoryCache, resolveConfig, resolveEmbedding, runPluginMigrations, sanitizeFTS, searchMMR, vecToBuffer, withLock, writePid };