brainbank 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/assets/architecture.png +0 -0
- package/bin/brainbank +18 -0
- package/bin/brainbank-mcp +19 -0
- package/dist/chunk-3YBCD6DI.js +117 -0
- package/dist/chunk-3YBCD6DI.js.map +1 -0
- package/dist/chunk-63GBCDS5.js +3249 -0
- package/dist/chunk-63GBCDS5.js.map +1 -0
- package/dist/chunk-DMFMTOHF.js +123 -0
- package/dist/chunk-DMFMTOHF.js.map +1 -0
- package/dist/chunk-FQYKWB2Q.js +136 -0
- package/dist/chunk-FQYKWB2Q.js.map +1 -0
- package/dist/chunk-IMJJ2VEM.js +74 -0
- package/dist/chunk-IMJJ2VEM.js.map +1 -0
- package/dist/chunk-M744PCJQ.js +43 -0
- package/dist/chunk-M744PCJQ.js.map +1 -0
- package/dist/chunk-O3J6ZIXK.js +82 -0
- package/dist/chunk-O3J6ZIXK.js.map +1 -0
- package/dist/chunk-OPH7GZ7U.js +124 -0
- package/dist/chunk-OPH7GZ7U.js.map +1 -0
- package/dist/chunk-PXEWQMN7.js +89 -0
- package/dist/chunk-PXEWQMN7.js.map +1 -0
- package/dist/chunk-RDQYDLYZ.js +69 -0
- package/dist/chunk-RDQYDLYZ.js.map +1 -0
- package/dist/chunk-VIIHPCC4.js +254 -0
- package/dist/chunk-VIIHPCC4.js.map +1 -0
- package/dist/chunk-WCQVDF3K.js +14 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3076 -0
- package/dist/cli.js.map +1 -0
- package/dist/haiku-expander-YRSIPGKP.js +8 -0
- package/dist/haiku-expander-YRSIPGKP.js.map +1 -0
- package/dist/haiku-pruner-SHAXUPY6.js +8 -0
- package/dist/haiku-pruner-SHAXUPY6.js.map +1 -0
- package/dist/http-server-QUXHLWUM.js +9 -0
- package/dist/http-server-QUXHLWUM.js.map +1 -0
- package/dist/index.d.ts +2161 -0
- package/dist/index.js +357 -0
- package/dist/index.js.map +1 -0
- package/dist/local-embedding-NZQTILGV.js +8 -0
- package/dist/local-embedding-NZQTILGV.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +334 -0
- package/dist/mcp.js.map +1 -0
- package/dist/openai-embedding-ZP5TSUJG.js +8 -0
- package/dist/openai-embedding-ZP5TSUJG.js.map +1 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
- package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
- package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
- package/dist/plugin-IKQ6IRSJ.js +32 -0
- package/dist/plugin-IKQ6IRSJ.js.map +1 -0
- package/dist/resolve-ASGLBNUC.js +10 -0
- package/dist/resolve-ASGLBNUC.js.map +1 -0
- package/dist/stats-tui-ZY2NQSEA.js +1904 -0
- package/dist/stats-tui-ZY2NQSEA.js.map +1 -0
- package/package.json +96 -0
- package/src/brainbank.ts +617 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +179 -0
- package/src/cli/commands/daemon.ts +100 -0
- package/src/cli/commands/docs.ts +71 -0
- package/src/cli/commands/files.ts +69 -0
- package/src/cli/commands/help.ts +77 -0
- package/src/cli/commands/index.ts +482 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp-export.ts +273 -0
- package/src/cli/commands/mcp.ts +6 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +336 -0
- package/src/cli/commands/search.ts +203 -0
- package/src/cli/commands/stats.ts +68 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +47 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +87 -0
- package/src/cli/factory/config-loader.ts +77 -0
- package/src/cli/factory/index.ts +69 -0
- package/src/cli/factory/plugin-loader.ts +325 -0
- package/src/cli/index.ts +71 -0
- package/src/cli/server-client.ts +178 -0
- package/src/cli/tui/index-tui.tsx +667 -0
- package/src/cli/tui/stats-data.ts +523 -0
- package/src/cli/tui/stats-search.ts +262 -0
- package/src/cli/tui/stats-tui.tsx +1465 -0
- package/src/cli/tui/tree-scanner.ts +650 -0
- package/src/cli/utils.ts +137 -0
- package/src/config.ts +49 -0
- package/src/constants.ts +21 -0
- package/src/db/adapter.ts +112 -0
- package/src/db/metadata.ts +130 -0
- package/src/db/migrations.ts +66 -0
- package/src/db/sqlite-adapter.ts +218 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +81 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +218 -0
- package/src/index.ts +154 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +126 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +71 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/mcp/mcp-server.ts +195 -0
- package/src/mcp/workspace-factory.ts +68 -0
- package/src/mcp/workspace-pool.ts +224 -0
- package/src/plugin.ts +381 -0
- package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
- package/src/providers/embeddings/embedding-worker.ts +141 -0
- package/src/providers/embeddings/local-embedding.ts +115 -0
- package/src/providers/embeddings/openai-embedding.ts +167 -0
- package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
- package/src/providers/embeddings/perplexity-embedding.ts +165 -0
- package/src/providers/embeddings/resolve.ts +34 -0
- package/src/providers/pruners/haiku-expander.ts +166 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/vector/hnsw-index.ts +174 -0
- package/src/providers/vector/hnsw-loader.ts +129 -0
- package/src/search/bm25-boost.ts +69 -0
- package/src/search/context-builder.ts +251 -0
- package/src/search/keyword/composite-bm25-search.ts +47 -0
- package/src/search/types.ts +37 -0
- package/src/search/vector/composite-vector-search.ts +61 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +384 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +336 -0
- package/src/services/kv-service.ts +64 -0
- package/src/services/plugin-registry.ts +77 -0
- package/src/services/watch.ts +340 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +493 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainBank — Stats TUI Search Session
|
|
3
|
+
*
|
|
4
|
+
* Wraps a BrainBank instance to provide staged search pipeline results
|
|
5
|
+
* for the interactive TUI. Captures each stage (raw → pruned → expanded)
|
|
6
|
+
* separately so the UI can display them side-by-side.
|
|
7
|
+
*
|
|
8
|
+
* Lazy initialization: the brain is created on first search.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SearchResult, Pruner, Expander, ExpanderManifestItem } from '@/types.ts';
|
|
12
|
+
import type { BrainBank } from '@/brainbank.ts';
|
|
13
|
+
|
|
14
|
+
import { createBrain } from '@/cli/factory/index.ts';
|
|
15
|
+
import { pruneResults } from '@/lib/prune.ts';
|
|
16
|
+
import { isExpandablePlugin } from '@/plugin.ts';
|
|
17
|
+
|
|
18
|
+
// ── Types ─────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/** Timings for each pipeline stage (ms). */
|
|
21
|
+
export interface PipelineTimings {
|
|
22
|
+
init: number;
|
|
23
|
+
search: number;
|
|
24
|
+
prune: number;
|
|
25
|
+
expand: number;
|
|
26
|
+
total: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Full result of a staged search pipeline run. */
|
|
30
|
+
export interface SearchPipelineResult {
|
|
31
|
+
/** All results from vector search (before pruning). */
|
|
32
|
+
raw: SearchResult[];
|
|
33
|
+
/** Results after LLM pruner filtered noise. Same order as pruner returned. */
|
|
34
|
+
pruned: SearchResult[];
|
|
35
|
+
/** Results dropped by pruner (raw minus pruned). */
|
|
36
|
+
dropped: SearchResult[];
|
|
37
|
+
/** Additional results discovered by LLM expander. */
|
|
38
|
+
expanded: SearchResult[];
|
|
39
|
+
/** Pipeline stage timings. */
|
|
40
|
+
timings: PipelineTimings;
|
|
41
|
+
/** Name of the active pruner (or null if none). */
|
|
42
|
+
prunerName: string | null;
|
|
43
|
+
/** Name of the active expander (or null if none). */
|
|
44
|
+
expanderName: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Source filter option — discovered from registered plugins. */
|
|
48
|
+
export interface SourceOption {
|
|
49
|
+
/** Source key: 'code', 'git', 'docs', etc. */
|
|
50
|
+
key: string;
|
|
51
|
+
/** Display label. */
|
|
52
|
+
label: string;
|
|
53
|
+
/** Whether this source is enabled for the current search. */
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
/** Max results for this source (default: 20). */
|
|
56
|
+
k: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Session ───────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export class BrainSearchSession {
|
|
62
|
+
private _brain: BrainBank | null = null;
|
|
63
|
+
private _repoPath: string;
|
|
64
|
+
private _initPromise: Promise<void> | null = null;
|
|
65
|
+
private _sources: SourceOption[] = [];
|
|
66
|
+
|
|
67
|
+
constructor(repoPath: string) {
|
|
68
|
+
this._repoPath = repoPath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Whether the brain has been initialized. */
|
|
72
|
+
get initialized(): boolean {
|
|
73
|
+
return this._brain !== null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Available source filters (populated after init). */
|
|
77
|
+
get sources(): readonly SourceOption[] {
|
|
78
|
+
return this._sources;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Pruner name (or null). */
|
|
82
|
+
get prunerName(): string | null {
|
|
83
|
+
return this._brain?.config.pruner?.constructor?.name ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Expander name (or null). */
|
|
87
|
+
get expanderName(): string | null {
|
|
88
|
+
return this._brain?.config.expander?.constructor?.name ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Initialize the BrainBank instance (lazy, idempotent).
|
|
93
|
+
* Creates the brain, loads HNSW + FTS indices.
|
|
94
|
+
*/
|
|
95
|
+
async init(): Promise<void> {
|
|
96
|
+
if (this._brain) return;
|
|
97
|
+
if (this._initPromise) return this._initPromise;
|
|
98
|
+
|
|
99
|
+
this._initPromise = this._doInit();
|
|
100
|
+
return this._initPromise;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async _doInit(): Promise<void> {
|
|
104
|
+
this._brain = await createBrain(this._repoPath);
|
|
105
|
+
await this._brain.initialize();
|
|
106
|
+
|
|
107
|
+
// Discover installed sources from plugin names (no 'all' — user selects individually)
|
|
108
|
+
this._sources = [];
|
|
109
|
+
const seen = new Set<string>();
|
|
110
|
+
for (const name of this._brain.plugins) {
|
|
111
|
+
const base = name.split(':')[0];
|
|
112
|
+
if (seen.has(base)) continue;
|
|
113
|
+
seen.add(base);
|
|
114
|
+
this._sources.push({
|
|
115
|
+
key: base,
|
|
116
|
+
label: base.charAt(0).toUpperCase() + base.slice(1),
|
|
117
|
+
enabled: true,
|
|
118
|
+
k: 20,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run the full search pipeline and return staged results.
|
|
125
|
+
*
|
|
126
|
+
* @param query - Search query
|
|
127
|
+
* @param activeSourceKeys - Set of active source keys (e.g. {'code', 'git'})
|
|
128
|
+
* @param usePruner - Whether to run the pruner stage
|
|
129
|
+
* @param useExpander - Whether to run the expander stage
|
|
130
|
+
*/
|
|
131
|
+
async search(
|
|
132
|
+
query: string,
|
|
133
|
+
activeSourceKeys?: Set<string>,
|
|
134
|
+
usePruner = true,
|
|
135
|
+
useExpander = true,
|
|
136
|
+
): Promise<SearchPipelineResult> {
|
|
137
|
+
if (!this._brain) throw new Error('BrainSearchSession not initialized');
|
|
138
|
+
|
|
139
|
+
const tTotal = Date.now();
|
|
140
|
+
const pruner = usePruner ? (this._brain.config.pruner as Pruner | undefined) : undefined;
|
|
141
|
+
const expander = useExpander ? (this._brain.config.expander as Expander | undefined) : undefined;
|
|
142
|
+
|
|
143
|
+
// Build sources filter — always pass K values so we don't fall back to DEFAULT_K=6
|
|
144
|
+
const sources: Record<string, number> = {};
|
|
145
|
+
for (const src of this._sources) {
|
|
146
|
+
const enabled = activeSourceKeys ? activeSourceKeys.has(src.key) : src.enabled;
|
|
147
|
+
sources[src.key] = enabled ? src.k : 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Stage 1: Vector search
|
|
151
|
+
const tSearch0 = Date.now();
|
|
152
|
+
const raw = await this._brain.search(query, {
|
|
153
|
+
sources,
|
|
154
|
+
source: 'cli',
|
|
155
|
+
});
|
|
156
|
+
const tSearch = Date.now() - tSearch0;
|
|
157
|
+
|
|
158
|
+
// Stage 2: Prune
|
|
159
|
+
let pruned = raw;
|
|
160
|
+
let dropped: SearchResult[] = [];
|
|
161
|
+
let tPrune = 0;
|
|
162
|
+
if (pruner && raw.length > 1) {
|
|
163
|
+
const pt0 = Date.now();
|
|
164
|
+
pruned = await pruneResults(query, raw, pruner);
|
|
165
|
+
tPrune = Date.now() - pt0;
|
|
166
|
+
const prunedSet = new Set(pruned);
|
|
167
|
+
dropped = raw.filter(r => !prunedSet.has(r));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Stage 3: Expand — filter results to only include active sources
|
|
171
|
+
let expanded: SearchResult[] = [];
|
|
172
|
+
let tExpand = 0;
|
|
173
|
+
if (expander && pruned.length > 0) {
|
|
174
|
+
const et0 = Date.now();
|
|
175
|
+
let expandedRaw = await this._runExpansion(query, pruned, expander);
|
|
176
|
+
// Filter expanded results by active sources
|
|
177
|
+
if (activeSourceKeys) {
|
|
178
|
+
expandedRaw = expandedRaw.filter(r => activeSourceKeys.has(r.type));
|
|
179
|
+
}
|
|
180
|
+
expanded = expandedRaw;
|
|
181
|
+
tExpand = Date.now() - et0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
raw,
|
|
186
|
+
pruned,
|
|
187
|
+
dropped,
|
|
188
|
+
expanded,
|
|
189
|
+
timings: {
|
|
190
|
+
init: 0,
|
|
191
|
+
search: tSearch,
|
|
192
|
+
prune: tPrune,
|
|
193
|
+
expand: tExpand,
|
|
194
|
+
total: Date.now() - tTotal,
|
|
195
|
+
},
|
|
196
|
+
prunerName: usePruner ? (pruner?.constructor?.name ?? null) : null,
|
|
197
|
+
expanderName: useExpander ? (expander?.constructor?.name ?? null) : null,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Run LLM expansion — mirrors ContextBuilder._expand logic. */
|
|
202
|
+
private async _runExpansion(
|
|
203
|
+
query: string,
|
|
204
|
+
results: SearchResult[],
|
|
205
|
+
expander: Expander,
|
|
206
|
+
): Promise<SearchResult[]> {
|
|
207
|
+
if (!this._brain) return [];
|
|
208
|
+
|
|
209
|
+
// Collect file paths and IDs already in results
|
|
210
|
+
const excludeFilePaths = [...new Set(
|
|
211
|
+
results.filter(r => r.filePath).map(r => r.filePath as string),
|
|
212
|
+
)];
|
|
213
|
+
const excludeIds: number[] = [];
|
|
214
|
+
for (const r of results) {
|
|
215
|
+
const meta = r.metadata as Record<string, unknown> | undefined;
|
|
216
|
+
const id = meta?.id as number | undefined;
|
|
217
|
+
if (id !== undefined) excludeIds.push(id);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Build manifest from ExpandablePlugins
|
|
221
|
+
const manifest: ExpanderManifestItem[] = [];
|
|
222
|
+
let resolver: ((ids: number[]) => SearchResult[]) | undefined;
|
|
223
|
+
|
|
224
|
+
// Access plugins via the brain's plugin list
|
|
225
|
+
// We need the registry... which is private. Use a workaround:
|
|
226
|
+
// BrainBank exposes `plugins` (names) but not instances.
|
|
227
|
+
// However, we can access it through the search functionality.
|
|
228
|
+
// The cleanest approach: use the brain's internal registry via
|
|
229
|
+
// a property we know exists on the class.
|
|
230
|
+
const registry = (this._brain as unknown as { _registry: { all: unknown[] } })._registry;
|
|
231
|
+
if (registry) {
|
|
232
|
+
for (const mod of registry.all) {
|
|
233
|
+
if (!isExpandablePlugin(mod as never)) continue;
|
|
234
|
+
const plugin = mod as { buildManifest: (fp: string[], ids: number[], rfp?: string[]) => ExpanderManifestItem[]; resolveChunks: (ids: number[]) => SearchResult[] };
|
|
235
|
+
manifest.push(...plugin.buildManifest(excludeFilePaths, excludeIds, excludeFilePaths));
|
|
236
|
+
if (!resolver) {
|
|
237
|
+
resolver = (ids: number[]) => plugin.resolveChunks(ids);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (manifest.length === 0 || !resolver) return [];
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const expandResult = await expander.expand(query, excludeIds, manifest);
|
|
246
|
+
if (expandResult.ids.length === 0) return [];
|
|
247
|
+
return resolver(expandResult.ids);
|
|
248
|
+
} catch {
|
|
249
|
+
// Fail-open: expansion errors are non-fatal
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Cleanup. */
|
|
255
|
+
close(): void {
|
|
256
|
+
if (this._brain) {
|
|
257
|
+
this._brain.close();
|
|
258
|
+
this._brain = null;
|
|
259
|
+
}
|
|
260
|
+
this._initPromise = null;
|
|
261
|
+
}
|
|
262
|
+
}
|