brainbank 0.1.3 → 0.1.4

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 (167) hide show
  1. package/README.md +84 -1107
  2. package/assets/architecture.png +0 -0
  3. package/bin/brainbank +8 -1
  4. package/bin/brainbank-mcp +19 -0
  5. package/dist/chunk-3UIWA32X.js +3341 -0
  6. package/dist/chunk-3UIWA32X.js.map +1 -0
  7. package/dist/chunk-3YBCD6DI.js +117 -0
  8. package/dist/chunk-3YBCD6DI.js.map +1 -0
  9. package/dist/chunk-DAGVUEXL.js +258 -0
  10. package/dist/chunk-DAGVUEXL.js.map +1 -0
  11. package/dist/chunk-DMFMTOHF.js +123 -0
  12. package/dist/chunk-DMFMTOHF.js.map +1 -0
  13. package/dist/chunk-FQYKWB2Q.js +136 -0
  14. package/dist/chunk-FQYKWB2Q.js.map +1 -0
  15. package/dist/chunk-IMJJ2VEM.js +74 -0
  16. package/dist/chunk-IMJJ2VEM.js.map +1 -0
  17. package/dist/chunk-M744PCJQ.js +43 -0
  18. package/dist/chunk-M744PCJQ.js.map +1 -0
  19. package/dist/chunk-NNDY7P2R.js +211 -0
  20. package/dist/chunk-NNDY7P2R.js.map +1 -0
  21. package/dist/chunk-O3J6ZIXK.js +82 -0
  22. package/dist/chunk-O3J6ZIXK.js.map +1 -0
  23. package/dist/chunk-RDQYDLYZ.js +69 -0
  24. package/dist/chunk-RDQYDLYZ.js.map +1 -0
  25. package/dist/chunk-WCQVDF3K.js +14 -0
  26. package/dist/cli.js +2713 -325
  27. package/dist/cli.js.map +1 -1
  28. package/dist/haiku-pruner-5KVT5AI2.js +8 -0
  29. package/dist/http-server-2ZQ6I43B.js +9 -0
  30. package/dist/index.d.ts +1886 -626
  31. package/dist/index.js +319 -46
  32. package/dist/index.js.map +1 -1
  33. package/dist/local-embedding-NZQTILGV.js +8 -0
  34. package/dist/mcp.d.ts +2 -0
  35. package/dist/mcp.js +386 -0
  36. package/dist/mcp.js.map +1 -0
  37. package/dist/openai-embedding-ZP5TSUJG.js +8 -0
  38. package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
  39. package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
  40. package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
  41. package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
  42. package/dist/plugin-IKQ6IRSJ.js +32 -0
  43. package/dist/plugin-IKQ6IRSJ.js.map +1 -0
  44. package/dist/resolve-ASGLBNUC.js +10 -0
  45. package/dist/resolve-ASGLBNUC.js.map +1 -0
  46. package/dist/stats-tui-AD3AMYGV.js +1904 -0
  47. package/dist/stats-tui-AD3AMYGV.js.map +1 -0
  48. package/package.json +38 -53
  49. package/src/brainbank.ts +617 -0
  50. package/src/cli/commands/collection.ts +77 -0
  51. package/src/cli/commands/context.ts +59 -0
  52. package/src/cli/commands/daemon.ts +100 -0
  53. package/src/cli/commands/docs.ts +71 -0
  54. package/src/cli/commands/files.ts +69 -0
  55. package/src/cli/commands/help.ts +82 -0
  56. package/src/cli/commands/index.ts +478 -0
  57. package/src/cli/commands/kv.ts +140 -0
  58. package/src/cli/commands/mcp-export.ts +273 -0
  59. package/src/cli/commands/mcp.ts +6 -0
  60. package/src/cli/commands/query.ts +167 -0
  61. package/src/cli/commands/reembed.ts +30 -0
  62. package/src/cli/commands/reindex.ts +40 -0
  63. package/src/cli/commands/scan.ts +336 -0
  64. package/src/cli/commands/search.ts +203 -0
  65. package/src/cli/commands/stats.ts +68 -0
  66. package/src/cli/commands/status.ts +47 -0
  67. package/src/cli/commands/watch.ts +47 -0
  68. package/src/cli/factory/brain-context.ts +43 -0
  69. package/src/cli/factory/builtin-registration.ts +87 -0
  70. package/src/cli/factory/config-loader.ts +77 -0
  71. package/src/cli/factory/index.ts +69 -0
  72. package/src/cli/factory/plugin-loader.ts +324 -0
  73. package/src/cli/index.ts +76 -0
  74. package/src/cli/server-client.ts +186 -0
  75. package/src/cli/tui/index-tui.tsx +667 -0
  76. package/src/cli/tui/stats-data.ts +523 -0
  77. package/src/cli/tui/stats-search.ts +262 -0
  78. package/src/cli/tui/stats-tui.tsx +1465 -0
  79. package/src/cli/tui/tree-scanner.ts +650 -0
  80. package/src/cli/utils.ts +137 -0
  81. package/src/config.ts +48 -0
  82. package/src/constants.ts +21 -0
  83. package/src/db/adapter.ts +112 -0
  84. package/src/db/metadata.ts +130 -0
  85. package/src/db/migrations.ts +66 -0
  86. package/src/db/sqlite-adapter.ts +218 -0
  87. package/src/db/tracker.ts +91 -0
  88. package/src/engine/index-api.ts +81 -0
  89. package/src/engine/reembed.ts +206 -0
  90. package/src/engine/search-api.ts +218 -0
  91. package/src/index.ts +150 -0
  92. package/src/lib/fts.ts +57 -0
  93. package/src/lib/languages.ts +179 -0
  94. package/src/lib/logger.ts +126 -0
  95. package/src/lib/math.ts +87 -0
  96. package/src/lib/provider-key.ts +20 -0
  97. package/src/lib/prune.ts +72 -0
  98. package/src/lib/rrf.ts +133 -0
  99. package/src/lib/write-lock.ts +108 -0
  100. package/src/mcp/mcp-server.ts +268 -0
  101. package/src/mcp/workspace-factory.ts +68 -0
  102. package/src/mcp/workspace-pool.ts +224 -0
  103. package/src/plugin.ts +381 -0
  104. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  105. package/src/providers/embeddings/embedding-worker.ts +141 -0
  106. package/src/providers/embeddings/local-embedding.ts +115 -0
  107. package/src/providers/embeddings/openai-embedding.ts +167 -0
  108. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  109. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  110. package/src/providers/embeddings/resolve.ts +34 -0
  111. package/src/providers/pruners/haiku-expander.ts +178 -0
  112. package/src/providers/pruners/haiku-pruner.ts +263 -0
  113. package/src/providers/vector/hnsw-index.ts +174 -0
  114. package/src/providers/vector/hnsw-loader.ts +129 -0
  115. package/src/search/bm25-boost.ts +76 -0
  116. package/src/search/context-builder.ts +209 -0
  117. package/src/search/keyword/composite-bm25-search.ts +47 -0
  118. package/src/search/query-decomposer.ts +124 -0
  119. package/src/search/types.ts +37 -0
  120. package/src/search/vector/composite-vector-search.ts +105 -0
  121. package/src/search/vector/mmr.ts +64 -0
  122. package/src/services/collection.ts +384 -0
  123. package/src/services/daemon.ts +87 -0
  124. package/src/services/http-server.ts +344 -0
  125. package/src/services/kv-service.ts +64 -0
  126. package/src/services/plugin-registry.ts +77 -0
  127. package/src/services/watch.ts +340 -0
  128. package/src/services/webhook-server.ts +100 -0
  129. package/src/types.ts +509 -0
  130. package/dist/chunk-2P3EGY6S.js +0 -37
  131. package/dist/chunk-2P3EGY6S.js.map +0 -1
  132. package/dist/chunk-3GAIDXRW.js +0 -105
  133. package/dist/chunk-3GAIDXRW.js.map +0 -1
  134. package/dist/chunk-4ZKBQ33J.js +0 -56
  135. package/dist/chunk-4ZKBQ33J.js.map +0 -1
  136. package/dist/chunk-7QVYU63E.js +0 -7
  137. package/dist/chunk-GOUBW7UA.js +0 -373
  138. package/dist/chunk-GOUBW7UA.js.map +0 -1
  139. package/dist/chunk-MJ3Y24H6.js +0 -185
  140. package/dist/chunk-MJ3Y24H6.js.map +0 -1
  141. package/dist/chunk-N6ZMBFDE.js +0 -224
  142. package/dist/chunk-N6ZMBFDE.js.map +0 -1
  143. package/dist/chunk-RAEBYV75.js +0 -709
  144. package/dist/chunk-RAEBYV75.js.map +0 -1
  145. package/dist/chunk-TW5NTYYZ.js +0 -2066
  146. package/dist/chunk-TW5NTYYZ.js.map +0 -1
  147. package/dist/chunk-Z5SU54HP.js +0 -171
  148. package/dist/chunk-Z5SU54HP.js.map +0 -1
  149. package/dist/code.d.ts +0 -31
  150. package/dist/code.js +0 -8
  151. package/dist/docs.d.ts +0 -19
  152. package/dist/docs.js +0 -8
  153. package/dist/git.d.ts +0 -31
  154. package/dist/git.js +0 -8
  155. package/dist/memory.d.ts +0 -19
  156. package/dist/memory.js +0 -146
  157. package/dist/memory.js.map +0 -1
  158. package/dist/notes.d.ts +0 -19
  159. package/dist/notes.js +0 -57
  160. package/dist/notes.js.map +0 -1
  161. package/dist/openai-PCTYLOWI.js +0 -8
  162. package/dist/types-Da_zLLOl.d.ts +0 -474
  163. /package/dist/{chunk-7QVYU63E.js.map → chunk-WCQVDF3K.js.map} +0 -0
  164. /package/dist/{code.js.map → haiku-pruner-5KVT5AI2.js.map} +0 -0
  165. /package/dist/{docs.js.map → http-server-2ZQ6I43B.js.map} +0 -0
  166. /package/dist/{git.js.map → local-embedding-NZQTILGV.js.map} +0 -0
  167. /package/dist/{openai-PCTYLOWI.js.map → openai-embedding-ZP5TSUJG.js.map} +0 -0
package/src/types.ts ADDED
@@ -0,0 +1,509 @@
1
+ /**
2
+ * BrainBank — Type Definitions
3
+ *
4
+ * All interfaces and types for the semantic knowledge bank.
5
+ */
6
+
7
+
8
+ import type { CollectionItem, CollectionSearchOptions, CollectionAddOptions } from './services/collection.ts';
9
+
10
+ // Re-export collection types so consumers don't need to import from services/
11
+ export type { CollectionItem, CollectionSearchOptions, CollectionAddOptions };
12
+
13
+
14
+ /** Public contract for a KV collection. Plugins depend on this interface, not the concrete class. */
15
+ export interface ICollection {
16
+ /** Collection name. */
17
+ readonly name: string;
18
+ /** Add an item. Returns its ID. */
19
+ add(content: string, options?: CollectionAddOptions | Record<string, unknown>): Promise<number>;
20
+ /** Update an item's content (re-embeds). Returns the new ID. */
21
+ update(id: number, content: string, options?: CollectionAddOptions): Promise<number>;
22
+ /** Add multiple items. Returns their IDs. */
23
+ addMany(items: { content: string; metadata?: Record<string, unknown>; tags?: string[]; ttl?: string }[]): Promise<number[]>;
24
+ /** Search this collection. */
25
+ search(query: string, options?: CollectionSearchOptions): Promise<CollectionItem[]>;
26
+ /** List items (newest first). */
27
+ list(options?: { limit?: number; offset?: number; tags?: string[] }): CollectionItem[];
28
+ /** Count items in this collection. */
29
+ count(): number;
30
+ /** Keep only the N most recent items. */
31
+ trim(options: { keep: number }): Promise<{ removed: number }>;
32
+ /** Remove items older than a duration string. */
33
+ prune(options: { olderThan: string }): Promise<{ removed: number }>;
34
+ /** Remove a specific item by ID. */
35
+ remove(id: number): void;
36
+ /** Clear all items in this collection. */
37
+ clear(): void;
38
+ }
39
+
40
+
41
+ export interface BrainBankConfig {
42
+ /** Root path of the repository to index. Default: '.' */
43
+ repoPath?: string;
44
+ /** SQLite database path. Default: '.brainbank/data/brainbank.db' */
45
+ dbPath?: string;
46
+
47
+ /** Max git commits to index. Default: 500 */
48
+ gitDepth?: number;
49
+ /** Max file size in bytes to index. Default: 512_000 (500KB) */
50
+ maxFileSize?: number;
51
+ /** Max diff bytes per commit. Default: 8192 */
52
+ maxDiffBytes?: number;
53
+ /** HNSW M parameter (connections per node). Default: 16 */
54
+ hnswM?: number;
55
+ /** HNSW efConstruction (build-time candidates). Default: 200 */
56
+ hnswEfConstruction?: number;
57
+ /** HNSW efSearch (query-time candidates). Default: 50 */
58
+ hnswEfSearch?: number;
59
+ /** Embedding dimensions. Default: 384 */
60
+ embeddingDims?: number;
61
+ /** Max HNSW elements. Default: 2_000_000 */
62
+ maxElements?: number;
63
+ /** Custom embedding provider (default: local WASM model) */
64
+ embeddingProvider?: EmbeddingProvider;
65
+ /** Optional LLM noise filter — drops irrelevant results before formatting */
66
+ pruner?: Pruner;
67
+ /** Optional LLM context expander — discovers additional relevant chunks after pruning */
68
+ expander?: Expander;
69
+ /** Port for optional webhook server (enables push-based watch plugins). */
70
+ webhookPort?: number;
71
+ /** Context field defaults from config.json "context" section. */
72
+ contextFields?: Record<string, unknown>;
73
+ }
74
+
75
+ export interface ResolvedConfig {
76
+ repoPath: string;
77
+ dbPath: string;
78
+ gitDepth: number;
79
+ maxFileSize: number;
80
+ maxDiffBytes: number;
81
+ hnswM: number;
82
+ hnswEfConstruction: number;
83
+ hnswEfSearch: number;
84
+ embeddingDims: number;
85
+ maxElements: number;
86
+ embeddingProvider?: EmbeddingProvider;
87
+ pruner?: Pruner;
88
+ expander?: Expander;
89
+ webhookPort?: number;
90
+ /** Context field defaults from config.json "context" section. */
91
+ contextFields?: Record<string, unknown>;
92
+ }
93
+
94
+
95
+ export interface EmbeddingProvider {
96
+ /** Vector dimensions produced by this provider. */
97
+ readonly dims: number;
98
+ /** Embed a single text string. */
99
+ embed(text: string): Promise<Float32Array>;
100
+ /** Embed multiple texts (batch). */
101
+ embedBatch(texts: string[]): Promise<Float32Array[]>;
102
+ /** Release resources. */
103
+ close(): Promise<void>;
104
+ }
105
+
106
+
107
+
108
+ /** Item passed to the pruner for noise classification. */
109
+ export interface PrunerItem {
110
+ /** Positional index (used to map back to SearchResult[]) */
111
+ id: number;
112
+ /** File path — primary signal for relevance */
113
+ filePath: string;
114
+ /** Trimmed content preview */
115
+ preview: string;
116
+ /** Chunk metadata (type, name, language, lines, etc.) */
117
+ metadata: Record<string, unknown>;
118
+ }
119
+
120
+ export interface Pruner {
121
+ /**
122
+ * Filter noise from search results and order by semantic relevance.
123
+ * @param query - The search query
124
+ * @param items - Items to evaluate (filePath + metadata + trimmed preview)
125
+ * @param context - Optional full task context (e.g. issue description, requirements)
126
+ * @returns Array of item IDs to KEEP, ordered by relevance to the query
127
+ * (most relevant first). Everything else is dropped.
128
+ */
129
+ prune(query: string, items: PrunerItem[], context?: string): Promise<number[]>;
130
+ /** Release resources. */
131
+ close?(): Promise<void>;
132
+ }
133
+
134
+ /** Lightweight chunk descriptor for the expander manifest. */
135
+ export interface ExpanderManifestItem {
136
+ /** Chunk ID (code_chunks.id) */
137
+ id: number;
138
+ /** File path */
139
+ filePath: string;
140
+ /** Chunk name (e.g. function/class name) */
141
+ name: string;
142
+ /** Chunk type (function, class, method, etc.) */
143
+ chunkType: string;
144
+ /** Line range (e.g. "L45-L89") */
145
+ lines: string;
146
+ /** True if this chunk is from an import-graph neighbor of the search results. */
147
+ priority?: boolean;
148
+ /** Brief content synopsis (~150 chars) for priority chunks — helps expander LLM understand chunk purpose. */
149
+ synopsis?: string;
150
+ }
151
+
152
+ /** Result from the expander: chunk IDs to add + optional free-text note. */
153
+ export interface ExpanderResult {
154
+ /** Additional chunk IDs to splice into results. */
155
+ ids: number[];
156
+ /** Brief observation for the agent (e.g. "file X not found", "module Y is deprecated"). */
157
+ note?: string;
158
+ }
159
+
160
+ export interface Expander {
161
+ /**
162
+ * Given a task and a manifest of available chunks,
163
+ * return additional chunk IDs + an optional contextual note.
164
+ * Runs after pruning. Fail-open: errors return empty result.
165
+ *
166
+ * @param query - The search query / task description
167
+ * @param currentIds - IDs of chunks already in results (from search + prune)
168
+ * @param manifest - All available chunks (lightweight descriptors)
169
+ * @param context - Optional full task context (e.g. issue description, requirements)
170
+ * @returns Chunk IDs to add + optional note
171
+ */
172
+ expand(query: string, currentIds: number[], manifest: ExpanderManifestItem[], context?: string): Promise<ExpanderResult>;
173
+ /** Release resources. */
174
+ close?(): Promise<void>;
175
+ }
176
+
177
+
178
+ export interface SearchHit {
179
+ id: number;
180
+ score: number;
181
+ }
182
+
183
+ export interface VectorIndex {
184
+ /** Initialize the index. Must be called before add/search. */
185
+ init(): Promise<this>;
186
+ /** Add a vector with an integer ID. Idempotent: duplicate IDs are skipped. */
187
+ add(vector: Float32Array, id: number): void;
188
+ /** Mark a vector as deleted so it no longer appears in searches. */
189
+ remove(id: number): void;
190
+ /** Search for k nearest neighbors. */
191
+ search(query: Float32Array, k: number): SearchHit[];
192
+ /** Clear all vectors and reset to empty state. */
193
+ reinit(): void;
194
+ /** Number of vectors in the index. */
195
+ readonly size: number;
196
+ }
197
+
198
+
199
+ export interface CodeChunk {
200
+ /** Auto-incremented DB id (set after insert) */
201
+ id?: number;
202
+ /** Relative file path from repo root */
203
+ filePath: string;
204
+ /** Chunk type: 'file' | 'function' | 'class' | 'block' */
205
+ chunkType: string;
206
+ /** Function/class name (if detected) */
207
+ name?: string;
208
+ /** Start line (1-indexed) */
209
+ startLine: number;
210
+ /** End line (1-indexed, inclusive) */
211
+ endLine: number;
212
+ /** Raw content of the chunk */
213
+ content: string;
214
+ /** Language identifier */
215
+ language: string;
216
+ }
217
+
218
+
219
+ export interface GitCommitRecord {
220
+ id?: number;
221
+ hash: string;
222
+ shortHash: string;
223
+ message: string;
224
+ author: string;
225
+ date: string;
226
+ timestamp: number;
227
+ filesChanged: string[];
228
+ diff?: string;
229
+ additions: number;
230
+ deletions: number;
231
+ isMerge: boolean;
232
+ }
233
+
234
+
235
+
236
+
237
+
238
+ export type SearchResultType = 'code' | 'commit' | 'document' | 'collection';
239
+
240
+ // Typed metadata per result type
241
+
242
+ export interface CodeResultMetadata {
243
+ /** Database chunk ID (used by call graph annotations). */
244
+ id?: number;
245
+ /** File path (may duplicate CodeResult.filePath for metadata-only access). */
246
+ filePath?: string;
247
+ /** Adjacent chunk IDs from the same file (used by context expansion). */
248
+ chunkIds?: number[];
249
+ chunkType: string;
250
+ name?: string;
251
+ startLine: number;
252
+ endLine: number;
253
+ language: string;
254
+ searchType?: string;
255
+ rrfScore?: number;
256
+ }
257
+
258
+ export interface CommitResultMetadata {
259
+ hash: string;
260
+ shortHash: string;
261
+ author: string;
262
+ date: string;
263
+ files: string[];
264
+ additions?: number;
265
+ deletions?: number;
266
+ diff?: string;
267
+ searchType?: string;
268
+ rrfScore?: number;
269
+ }
270
+
271
+
272
+
273
+ export interface DocumentResultMetadata {
274
+ collection?: string;
275
+ title?: string;
276
+ seq?: number;
277
+ path?: string;
278
+ searchType?: string;
279
+ /** Internal chunk ID used by hybrid search to map fused results. */
280
+ chunkId?: number;
281
+ rrfScore?: number;
282
+ }
283
+
284
+ // Discriminated union
285
+
286
+ export interface CodeResult {
287
+ type: 'code';
288
+ score: number;
289
+ filePath: string;
290
+ content: string;
291
+ context?: string;
292
+ metadata: CodeResultMetadata;
293
+ }
294
+
295
+ export interface CommitResult {
296
+ type: 'commit';
297
+ score: number;
298
+ filePath?: string;
299
+ content: string;
300
+ context?: string;
301
+ metadata: CommitResultMetadata;
302
+ }
303
+
304
+
305
+
306
+ export interface DocumentResult {
307
+ type: 'document';
308
+ score: number;
309
+ filePath: string;
310
+ content: string;
311
+ context?: string;
312
+ metadata: DocumentResultMetadata;
313
+ }
314
+
315
+ export interface CollectionResultMetadata {
316
+ id?: number;
317
+ collection?: string;
318
+ rrfScore?: number;
319
+ [key: string]: unknown;
320
+ }
321
+
322
+ export interface CollectionResult {
323
+ type: 'collection';
324
+ score: number;
325
+ filePath?: string;
326
+ content: string;
327
+ context?: string;
328
+ metadata: CollectionResultMetadata;
329
+ }
330
+
331
+ export type SearchResult = CodeResult | CommitResult | DocumentResult | CollectionResult;
332
+
333
+
334
+ /** Narrow a SearchResult to CodeResult. */
335
+ export function isCodeResult(r: SearchResult): r is CodeResult {
336
+ return r.type === 'code';
337
+ }
338
+ /** Narrow a SearchResult to CommitResult. */
339
+ export function isCommitResult(r: SearchResult): r is CommitResult {
340
+ return r.type === 'commit';
341
+ }
342
+ /** Narrow a SearchResult to DocumentResult. */
343
+ export function isDocumentResult(r: SearchResult): r is DocumentResult {
344
+ return r.type === 'document';
345
+ }
346
+
347
+ /** Narrow a SearchResult to CollectionResult. */
348
+ export function isCollectionResult(r: SearchResult): r is CollectionResult {
349
+ return r.type === 'collection';
350
+ }
351
+
352
+
353
+ type MatchHandlers<T> = {
354
+ code?: (r: CodeResult) => T;
355
+ commit?: (r: CommitResult) => T;
356
+ document?: (r: DocumentResult) => T;
357
+ collection?: (r: CollectionResult) => T;
358
+ _?: (r: SearchResult) => T;
359
+ };
360
+
361
+ /**
362
+ * Pattern-match on SearchResult type. Calls the matching handler
363
+ * or the `_` fallback. Returns undefined if no handler matches.
364
+ */
365
+ export function matchResult<T>(
366
+ result: SearchResult,
367
+ handlers: MatchHandlers<T>,
368
+ ): T | undefined {
369
+ switch (result.type) {
370
+ case 'code': return (handlers.code ?? handlers._)?.(result);
371
+ case 'commit': return (handlers.commit ?? handlers._)?.(result);
372
+ case 'document': return (handlers.document ?? handlers._)?.(result);
373
+ case 'collection': return (handlers.collection ?? handlers._)?.(result);
374
+ }
375
+ }
376
+
377
+
378
+ export interface ContextOptions {
379
+ /** Per-source result limits. Built-in: 'code', 'git'. Default: { code: 6, git: 5 } */
380
+ sources?: Record<string, number>;
381
+ /** Files the agent is about to modify (improves co-edit suggestions) */
382
+ affectedFiles?: string[];
383
+ /** Minimum similarity score threshold. Default: 0.25 */
384
+ minScore?: number;
385
+ /** Use MMR for diversity. Default: true */
386
+ useMMR?: boolean;
387
+ /** MMR lambda (0 = diversity, 1 = relevance). Default: 0.7 */
388
+ mmrLambda?: number;
389
+ /** Filter results to files under these path prefixes (e.g. 'src/services/' or ['src/', 'lib/']). */
390
+ pathPrefix?: string | string[];
391
+ /** Exclude results whose filePath starts with any of these prefixes (e.g. ['src/tests/', 'src/mocks/']). */
392
+ ignorePaths?: string[];
393
+ /** File paths to exclude from results (e.g. files already returned in a previous query). */
394
+ excludeFiles?: Set<string>;
395
+ /** Optional per-request pruner override (e.g. HaikuPruner for LLM noise filtering). */
396
+ pruner?: Pruner;
397
+ /** Caller origin for debug logging. */
398
+ source?: 'cli' | 'mcp' | 'daemon' | 'api';
399
+ /**
400
+ * General task context for the pruner — provides background signal.
401
+ * Pass a GitHub issue body, requirements doc, or conversation context.
402
+ * The pruner uses this to understand the broader task.
403
+ */
404
+ context?: string;
405
+ /**
406
+ * Specific pruning instructions — tells the pruner exactly what to focus on.
407
+ * E.g. "only keep files related to WebSocket JWT refresh".
408
+ * Both `context` and `pruner` are merged and sent to the Haiku pruner.
409
+ */
410
+ prunerContext?: string;
411
+ /**
412
+ * Context field overrides. Merged on top of config.json "context" defaults.
413
+ * Plugin-defined fields like `lines`, `callTree`, `symbols`, `compact`, `imports`.
414
+ * Example: `{ lines: true, callTree: { depth: 3 }, symbols: true }`
415
+ */
416
+ fields?: Record<string, unknown>;
417
+ }
418
+
419
+
420
+ export interface DocumentCollection {
421
+ /** Collection name (e.g. 'notes', 'docs') */
422
+ name: string;
423
+ /** Directory path to index */
424
+ path: string;
425
+ /** Glob pattern for files (default: all markdown) */
426
+ pattern?: string;
427
+ /** Glob patterns to ignore */
428
+ ignore?: string[];
429
+ /** Context description for this collection */
430
+ context?: string;
431
+ }
432
+
433
+ export interface DocChunk {
434
+ id?: number;
435
+ /** Collection name */
436
+ collection: string;
437
+ /** Relative file path within the collection */
438
+ filePath: string;
439
+ /** Document title (first heading or filename) */
440
+ title: string;
441
+ /** Chunk content */
442
+ content: string;
443
+ /** Chunk sequence within the document (0, 1, 2...) */
444
+ seq: number;
445
+ /** Character position in original document */
446
+ pos: number;
447
+ /** Content hash for incremental updates */
448
+ contentHash: string;
449
+ }
450
+
451
+
452
+ /** Plugin-provided stats. Key is the plugin name. */
453
+ export interface IndexStats {
454
+ [pluginName: string]: Record<string, number | string> | undefined;
455
+ }
456
+
457
+
458
+ /** File-level progress (used by indexers). */
459
+ export type ProgressCallback = (file: string, current: number, total: number) => void;
460
+
461
+ /** Stage-level progress (used by BrainBank.index() orchestrator). */
462
+ export type StageProgressCallback = (stage: string, message: string) => void;
463
+
464
+ export interface IndexResult {
465
+ indexed: number;
466
+ skipped: number;
467
+ chunks?: number;
468
+ removed?: number;
469
+ }
470
+
471
+
472
+ export interface CoEditSuggestion {
473
+ file: string;
474
+ count: number;
475
+ }
476
+
477
+
478
+ /** Generalized watch event — works for files, APIs, webhooks. */
479
+ export interface WatchEvent {
480
+ /** Event type. 'sync' is for batch/poll sources that don't distinguish CRUD. */
481
+ type: 'create' | 'update' | 'delete' | 'sync';
482
+ /** Unique ID of the changed item (file path, PR#123, PROJ-456, etc.). */
483
+ sourceId: string;
484
+ /** Source descriptor (e.g. 'file', 'github:pr', 'jira:card'). */
485
+ sourceName: string;
486
+ /** Optional raw payload to avoid re-fetching. */
487
+ payload?: unknown;
488
+ }
489
+
490
+ /** Callback that plugins invoke when they detect a change. */
491
+ export type WatchEventHandler = (event: WatchEvent) => void;
492
+
493
+ /** Lifecycle handle returned by WatchablePlugin.watch(). */
494
+ export interface WatchHandle {
495
+ /** Stop watching and release resources. */
496
+ stop(): Promise<void>;
497
+ /** Whether the watcher is still active. */
498
+ readonly active: boolean;
499
+ }
500
+
501
+ /** Optional hints from plugin to core — debounce, batching, priority. */
502
+ export interface WatchConfig {
503
+ /** Debounce interval in ms. 0 = process immediately. Default: inherited from WatchOptions. */
504
+ debounceMs?: number;
505
+ /** Max events to batch before triggering re-index. Default: unlimited. */
506
+ batchSize?: number;
507
+ /** Processing priority. Default: 'realtime'. */
508
+ priority?: 'realtime' | 'background';
509
+ }
@@ -1,37 +0,0 @@
1
- import {
2
- __name
3
- } from "./chunk-7QVYU63E.js";
4
-
5
- // src/embeddings/math.ts
6
- function cosineSimilarity(a, b) {
7
- if (a.length !== b.length) {
8
- throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
9
- }
10
- if (a.length === 0) return 0;
11
- let dot = 0;
12
- for (let i = 0; i < a.length; i++) {
13
- dot += a[i] * b[i];
14
- }
15
- return dot;
16
- }
17
- __name(cosineSimilarity, "cosineSimilarity");
18
- function normalize(vec) {
19
- let norm = 0;
20
- for (let i = 0; i < vec.length; i++) {
21
- norm += vec[i] * vec[i];
22
- }
23
- norm = Math.sqrt(norm);
24
- if (norm === 0) return new Float32Array(vec.length);
25
- const result = new Float32Array(vec.length);
26
- for (let i = 0; i < vec.length; i++) {
27
- result[i] = vec[i] / norm;
28
- }
29
- return result;
30
- }
31
- __name(normalize, "normalize");
32
-
33
- export {
34
- cosineSimilarity,
35
- normalize
36
- };
37
- //# sourceMappingURL=chunk-2P3EGY6S.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/embeddings/math.ts"],"sourcesContent":["/**\n * BrainBank — Math Utilities\n * \n * Pure vector math functions for similarity calculations.\n * No dependencies — works on Float32Array directly.\n */\n\n/**\n * Cosine similarity between two vectors.\n * Assumes vectors are already normalized (unit length).\n * Returns value between -1.0 and 1.0.\n */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n if (a.length === 0) return 0;\n\n let dot = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n }\n return dot;\n}\n\n/**\n * Full cosine similarity (normalizes first).\n * Use this when vectors may not be pre-normalized.\n */\nexport function cosineSimilarityFull(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n if (a.length === 0) return 0;\n\n let dot = 0, normA = 0, normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/**\n * L2-normalize a vector to unit length.\n * Returns a new Float32Array.\n */\nexport function normalize(vec: Float32Array): Float32Array {\n let norm = 0;\n for (let i = 0; i < vec.length; i++) {\n norm += vec[i] * vec[i];\n }\n norm = Math.sqrt(norm);\n if (norm === 0) return new Float32Array(vec.length);\n\n const result = new Float32Array(vec.length);\n for (let i = 0; i < vec.length; i++) {\n result[i] = vec[i] / norm;\n }\n return result;\n}\n\n/**\n * Euclidean distance between two vectors.\n */\nexport function euclideanDistance(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n let sum = 0;\n for (let i = 0; i < a.length; i++) {\n const d = a[i] - b[i];\n sum += d * d;\n }\n return Math.sqrt(sum);\n}\n"],"mappings":";;;;;AAYO,SAAS,iBAAiB,GAAiB,GAAyB;AACvE,MAAI,EAAE,WAAW,EAAE,QAAQ;AACvB,UAAM,IAAI,MAAM,8BAA8B,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE;AAAA,EAC3E;AACA,MAAI,EAAE,WAAW,EAAG,QAAO;AAE3B,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,WAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,EACrB;AACA,SAAO;AACX;AAXgB;AAqCT,SAAS,UAAU,KAAiC;AACvD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,YAAQ,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,EAC1B;AACA,SAAO,KAAK,KAAK,IAAI;AACrB,MAAI,SAAS,EAAG,QAAO,IAAI,aAAa,IAAI,MAAM;AAElD,QAAM,SAAS,IAAI,aAAa,IAAI,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,WAAO,CAAC,IAAI,IAAI,CAAC,IAAI;AAAA,EACzB;AACA,SAAO;AACX;AAbgB;","names":[]}
@@ -1,105 +0,0 @@
1
- import {
2
- __name
3
- } from "./chunk-7QVYU63E.js";
4
-
5
- // src/embeddings/openai.ts
6
- var DEFAULT_MODEL = "text-embedding-3-small";
7
- var DEFAULT_DIMS = {
8
- "text-embedding-3-small": 1536,
9
- "text-embedding-3-large": 3072,
10
- "text-embedding-ada-002": 1536
11
- };
12
- var API_URL = "https://api.openai.com/v1/embeddings";
13
- var MAX_BATCH = 100;
14
- var OpenAIEmbedding = class {
15
- static {
16
- __name(this, "OpenAIEmbedding");
17
- }
18
- dims;
19
- _apiKey;
20
- _model;
21
- _baseUrl;
22
- _requestDims;
23
- _retrying = false;
24
- constructor(options = {}) {
25
- this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
26
- this._model = options.model ?? DEFAULT_MODEL;
27
- this._baseUrl = options.baseUrl ?? API_URL;
28
- if (options.dims && this._model.startsWith("text-embedding-3")) {
29
- this._requestDims = options.dims;
30
- this.dims = options.dims;
31
- } else {
32
- this.dims = options.dims ?? DEFAULT_DIMS[this._model] ?? 1536;
33
- }
34
- }
35
- async embed(text) {
36
- const results = await this._request([text]);
37
- return results[0];
38
- }
39
- async embedBatch(texts) {
40
- if (texts.length === 0) return [];
41
- const results = [];
42
- for (let i = 0; i < texts.length; i += MAX_BATCH) {
43
- const batch = texts.slice(i, i + MAX_BATCH);
44
- const embeddings = await this._request(batch);
45
- results.push(...embeddings);
46
- }
47
- return results;
48
- }
49
- async close() {
50
- }
51
- _isTokenLimitError(errText) {
52
- return errText.includes("maximum input length") || errText.includes("maximum context length") || errText.includes("too many tokens");
53
- }
54
- async _request(input) {
55
- if (!this._apiKey) {
56
- throw new Error("OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.");
57
- }
58
- const MAX_CHARS = 24e3;
59
- const safeInput = input.map((t) => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);
60
- const body = {
61
- model: this._model,
62
- input: safeInput
63
- };
64
- if (this._requestDims) {
65
- body.dimensions = this._requestDims;
66
- }
67
- const res = await fetch(this._baseUrl, {
68
- method: "POST",
69
- headers: {
70
- "Content-Type": "application/json",
71
- "Authorization": `Bearer ${this._apiKey}`
72
- },
73
- body: JSON.stringify(body)
74
- });
75
- if (!res.ok) {
76
- const err = await res.text();
77
- const isTokenLimit = res.status === 400 && this._isTokenLimitError(err);
78
- if (isTokenLimit && safeInput.length > 1) {
79
- const results = [];
80
- for (const text of safeInput) {
81
- const r = await this._request([text.slice(0, 8e3)]);
82
- results.push(r[0]);
83
- }
84
- return results;
85
- }
86
- if (isTokenLimit && safeInput.length === 1 && !this._retrying) {
87
- this._retrying = true;
88
- try {
89
- return await this._request([safeInput[0].slice(0, 6e3)]);
90
- } finally {
91
- this._retrying = false;
92
- }
93
- }
94
- throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);
95
- }
96
- const json = await res.json();
97
- const sorted = json.data.sort((a, b) => a.index - b.index);
98
- return sorted.map((d) => new Float32Array(d.embedding));
99
- }
100
- };
101
-
102
- export {
103
- OpenAIEmbedding
104
- };
105
- //# sourceMappingURL=chunk-3GAIDXRW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/embeddings/openai.ts"],"sourcesContent":["/**\n * BrainBank — OpenAI Embedding Provider\n * \n * Uses OpenAI's embedding API via fetch (no SDK dependency).\n * Supports text-embedding-3-small, text-embedding-3-large, and ada-002.\n * \n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new OpenAIEmbedding({ model: 'text-embedding-3-small' }),\n * });\n */\n\nimport type { EmbeddingProvider } from '../types.ts';\n\nconst DEFAULT_MODEL = 'text-embedding-3-small';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'text-embedding-3-small': 1536,\n 'text-embedding-3-large': 3072,\n 'text-embedding-ada-002': 1536,\n};\nconst API_URL = 'https://api.openai.com/v1/embeddings';\nconst MAX_BATCH = 100; // OpenAI limit per request\n\nexport interface OpenAIEmbeddingOptions {\n /** OpenAI API key. Falls back to OPENAI_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'text-embedding-3-small' */\n model?: string;\n /** Vector dimensions. If omitted, uses model default. text-embedding-3-* supports custom dims. */\n dims?: number;\n /** Base URL override (for Azure, proxies, etc.) */\n baseUrl?: string;\n}\n\nexport class OpenAIEmbedding implements EmbeddingProvider {\n readonly dims: number;\n\n private _apiKey: string;\n private _model: string;\n private _baseUrl: string;\n private _requestDims: number | undefined;\n private _retrying = false;\n\n constructor(options: OpenAIEmbeddingOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n this._baseUrl = options.baseUrl ?? API_URL;\n\n // Custom dims only supported by text-embedding-3-*\n if (options.dims && this._model.startsWith('text-embedding-3')) {\n this._requestDims = options.dims;\n this.dims = options.dims;\n } else {\n this.dims = options.dims ?? DEFAULT_DIMS[this._model] ?? 1536;\n }\n }\n\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const results: Float32Array[] = [];\n\n // Split into chunks of MAX_BATCH\n for (let i = 0; i < texts.length; i += MAX_BATCH) {\n const batch = texts.slice(i, i + MAX_BATCH);\n const embeddings = await this._request(batch);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n private _isTokenLimitError(errText: string): boolean {\n return errText.includes('maximum input length') ||\n errText.includes('maximum context length') ||\n errText.includes('too many tokens');\n }\n\n private async _request(input: string[]): Promise<Float32Array[]> {\n if (!this._apiKey) {\n throw new Error('OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.');\n }\n\n // Truncate texts that would exceed token limit (~4 chars per token, 8192 max)\n const MAX_CHARS = 24_000;\n const safeInput = input.map(t => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);\n\n const body: Record<string, any> = {\n model: this._model,\n input: safeInput,\n };\n\n if (this._requestDims) {\n body.dimensions = this._requestDims;\n }\n\n const res = await fetch(this._baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const err = await res.text();\n const isTokenLimit = res.status === 400 && this._isTokenLimitError(err);\n\n // If token limit error in a batch, retry each item individually with more aggressive truncation\n if (isTokenLimit && safeInput.length > 1) {\n const results: Float32Array[] = [];\n for (const text of safeInput) {\n const r = await this._request([text.slice(0, 8_000)]);\n results.push(r[0]);\n }\n return results;\n }\n // Last resort: if single item still fails, truncate to ~2k tokens\n if (isTokenLimit && safeInput.length === 1 && !this._retrying) {\n this._retrying = true;\n try {\n return await this._request([safeInput[0].slice(0, 6_000)]);\n } finally {\n this._retrying = false;\n }\n }\n throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);\n }\n\n const json = await res.json() as {\n data: Array<{ embedding: number[]; index: number }>;\n };\n\n // Sort by index (API may return out of order)\n const sorted = json.data.sort((a, b) => a.index - b.index);\n\n return sorted.map(d => new Float32Array(d.embedding));\n }\n}\n"],"mappings":"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","names":[]}