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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/assets/architecture.png +0 -0
  4. package/bin/brainbank +18 -0
  5. package/bin/brainbank-mcp +19 -0
  6. package/dist/chunk-3YBCD6DI.js +117 -0
  7. package/dist/chunk-3YBCD6DI.js.map +1 -0
  8. package/dist/chunk-63GBCDS5.js +3249 -0
  9. package/dist/chunk-63GBCDS5.js.map +1 -0
  10. package/dist/chunk-DMFMTOHF.js +123 -0
  11. package/dist/chunk-DMFMTOHF.js.map +1 -0
  12. package/dist/chunk-FQYKWB2Q.js +136 -0
  13. package/dist/chunk-FQYKWB2Q.js.map +1 -0
  14. package/dist/chunk-IMJJ2VEM.js +74 -0
  15. package/dist/chunk-IMJJ2VEM.js.map +1 -0
  16. package/dist/chunk-M744PCJQ.js +43 -0
  17. package/dist/chunk-M744PCJQ.js.map +1 -0
  18. package/dist/chunk-O3J6ZIXK.js +82 -0
  19. package/dist/chunk-O3J6ZIXK.js.map +1 -0
  20. package/dist/chunk-OPH7GZ7U.js +124 -0
  21. package/dist/chunk-OPH7GZ7U.js.map +1 -0
  22. package/dist/chunk-PXEWQMN7.js +89 -0
  23. package/dist/chunk-PXEWQMN7.js.map +1 -0
  24. package/dist/chunk-RDQYDLYZ.js +69 -0
  25. package/dist/chunk-RDQYDLYZ.js.map +1 -0
  26. package/dist/chunk-VIIHPCC4.js +254 -0
  27. package/dist/chunk-VIIHPCC4.js.map +1 -0
  28. package/dist/chunk-WCQVDF3K.js +14 -0
  29. package/dist/chunk-WCQVDF3K.js.map +1 -0
  30. package/dist/cli.d.ts +1 -0
  31. package/dist/cli.js +3076 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/haiku-expander-YRSIPGKP.js +8 -0
  34. package/dist/haiku-expander-YRSIPGKP.js.map +1 -0
  35. package/dist/haiku-pruner-SHAXUPY6.js +8 -0
  36. package/dist/haiku-pruner-SHAXUPY6.js.map +1 -0
  37. package/dist/http-server-QUXHLWUM.js +9 -0
  38. package/dist/http-server-QUXHLWUM.js.map +1 -0
  39. package/dist/index.d.ts +2161 -0
  40. package/dist/index.js +357 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/local-embedding-NZQTILGV.js +8 -0
  43. package/dist/local-embedding-NZQTILGV.js.map +1 -0
  44. package/dist/mcp.d.ts +2 -0
  45. package/dist/mcp.js +334 -0
  46. package/dist/mcp.js.map +1 -0
  47. package/dist/openai-embedding-ZP5TSUJG.js +8 -0
  48. package/dist/openai-embedding-ZP5TSUJG.js.map +1 -0
  49. package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
  50. package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
  51. package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
  52. package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
  53. package/dist/plugin-IKQ6IRSJ.js +32 -0
  54. package/dist/plugin-IKQ6IRSJ.js.map +1 -0
  55. package/dist/resolve-ASGLBNUC.js +10 -0
  56. package/dist/resolve-ASGLBNUC.js.map +1 -0
  57. package/dist/stats-tui-ZY2NQSEA.js +1904 -0
  58. package/dist/stats-tui-ZY2NQSEA.js.map +1 -0
  59. package/package.json +96 -0
  60. package/src/brainbank.ts +617 -0
  61. package/src/cli/commands/collection.ts +77 -0
  62. package/src/cli/commands/context.ts +179 -0
  63. package/src/cli/commands/daemon.ts +100 -0
  64. package/src/cli/commands/docs.ts +71 -0
  65. package/src/cli/commands/files.ts +69 -0
  66. package/src/cli/commands/help.ts +77 -0
  67. package/src/cli/commands/index.ts +482 -0
  68. package/src/cli/commands/kv.ts +140 -0
  69. package/src/cli/commands/mcp-export.ts +273 -0
  70. package/src/cli/commands/mcp.ts +6 -0
  71. package/src/cli/commands/reembed.ts +30 -0
  72. package/src/cli/commands/scan.ts +336 -0
  73. package/src/cli/commands/search.ts +203 -0
  74. package/src/cli/commands/stats.ts +68 -0
  75. package/src/cli/commands/status.ts +47 -0
  76. package/src/cli/commands/watch.ts +47 -0
  77. package/src/cli/factory/brain-context.ts +43 -0
  78. package/src/cli/factory/builtin-registration.ts +87 -0
  79. package/src/cli/factory/config-loader.ts +77 -0
  80. package/src/cli/factory/index.ts +69 -0
  81. package/src/cli/factory/plugin-loader.ts +325 -0
  82. package/src/cli/index.ts +71 -0
  83. package/src/cli/server-client.ts +178 -0
  84. package/src/cli/tui/index-tui.tsx +667 -0
  85. package/src/cli/tui/stats-data.ts +523 -0
  86. package/src/cli/tui/stats-search.ts +262 -0
  87. package/src/cli/tui/stats-tui.tsx +1465 -0
  88. package/src/cli/tui/tree-scanner.ts +650 -0
  89. package/src/cli/utils.ts +137 -0
  90. package/src/config.ts +49 -0
  91. package/src/constants.ts +21 -0
  92. package/src/db/adapter.ts +112 -0
  93. package/src/db/metadata.ts +130 -0
  94. package/src/db/migrations.ts +66 -0
  95. package/src/db/sqlite-adapter.ts +218 -0
  96. package/src/db/tracker.ts +91 -0
  97. package/src/engine/index-api.ts +81 -0
  98. package/src/engine/reembed.ts +206 -0
  99. package/src/engine/search-api.ts +218 -0
  100. package/src/index.ts +154 -0
  101. package/src/lib/fts.ts +57 -0
  102. package/src/lib/languages.ts +180 -0
  103. package/src/lib/logger.ts +126 -0
  104. package/src/lib/math.ts +87 -0
  105. package/src/lib/provider-key.ts +20 -0
  106. package/src/lib/prune.ts +71 -0
  107. package/src/lib/rrf.ts +133 -0
  108. package/src/lib/write-lock.ts +108 -0
  109. package/src/mcp/mcp-server.ts +195 -0
  110. package/src/mcp/workspace-factory.ts +68 -0
  111. package/src/mcp/workspace-pool.ts +224 -0
  112. package/src/plugin.ts +381 -0
  113. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  114. package/src/providers/embeddings/embedding-worker.ts +141 -0
  115. package/src/providers/embeddings/local-embedding.ts +115 -0
  116. package/src/providers/embeddings/openai-embedding.ts +167 -0
  117. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  118. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  119. package/src/providers/embeddings/resolve.ts +34 -0
  120. package/src/providers/pruners/haiku-expander.ts +166 -0
  121. package/src/providers/pruners/haiku-pruner.ts +112 -0
  122. package/src/providers/vector/hnsw-index.ts +174 -0
  123. package/src/providers/vector/hnsw-loader.ts +129 -0
  124. package/src/search/bm25-boost.ts +69 -0
  125. package/src/search/context-builder.ts +251 -0
  126. package/src/search/keyword/composite-bm25-search.ts +47 -0
  127. package/src/search/types.ts +37 -0
  128. package/src/search/vector/composite-vector-search.ts +61 -0
  129. package/src/search/vector/mmr.ts +64 -0
  130. package/src/services/collection.ts +384 -0
  131. package/src/services/daemon.ts +87 -0
  132. package/src/services/http-server.ts +336 -0
  133. package/src/services/kv-service.ts +64 -0
  134. package/src/services/plugin-registry.ts +77 -0
  135. package/src/services/watch.ts +340 -0
  136. package/src/services/webhook-server.ts +100 -0
  137. 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
+ }