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,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainBank — Search API
|
|
3
|
+
*
|
|
4
|
+
* Thin orchestrator for all search operations.
|
|
5
|
+
* Pipeline: collect → fuse (RRF).
|
|
6
|
+
*
|
|
7
|
+
* Plugin-agnostic — discovers vector strategies and searchable plugins
|
|
8
|
+
* via capability interfaces. No hardcoded plugin names.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { DatabaseAdapter } from '@/db/adapter.ts';
|
|
12
|
+
import type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';
|
|
13
|
+
import type { SearchStrategy, SearchOptions, DomainVectorSearch } from '@/search/types.ts';
|
|
14
|
+
import type { KVService } from '@/services/kv-service.ts';
|
|
15
|
+
import type { PluginRegistry } from '@/services/plugin-registry.ts';
|
|
16
|
+
import type { ResolvedConfig, EmbeddingProvider, SearchResult, ContextOptions } from '@/types.ts';
|
|
17
|
+
|
|
18
|
+
import { isVectorSearchPlugin, isSearchable, isCoEditPlugin, isContextFormatterPlugin } from '@/plugin.ts';
|
|
19
|
+
import { reciprocalRankFusion } from '@/lib/rrf.ts';
|
|
20
|
+
import { filterByPath } from '@/search/bm25-boost.ts';
|
|
21
|
+
import { ContextBuilder } from '@/search/context-builder.ts';
|
|
22
|
+
import { CompositeBM25Search } from '@/search/keyword/composite-bm25-search.ts';
|
|
23
|
+
import { CompositeVectorSearch } from '@/search/vector/composite-vector-search.ts';
|
|
24
|
+
import { logQuery } from '@/lib/logger.ts';
|
|
25
|
+
import type { QueryLogResult } from '@/lib/logger.ts';
|
|
26
|
+
import { providerKey } from '@/lib/provider-key.ts';
|
|
27
|
+
|
|
28
|
+
/** Dependencies injected at construction time. */
|
|
29
|
+
export interface SearchAPIDeps {
|
|
30
|
+
search?: SearchStrategy;
|
|
31
|
+
bm25?: SearchStrategy;
|
|
32
|
+
registry: PluginRegistry;
|
|
33
|
+
config: ResolvedConfig;
|
|
34
|
+
kvService: KVService;
|
|
35
|
+
contextBuilder?: ContextBuilder;
|
|
36
|
+
embedding: EmbeddingProvider;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a fully-wired SearchAPI from registry state.
|
|
41
|
+
* Discovers vector strategies from VectorSearchPlugin capability.
|
|
42
|
+
* Always returns an instance — handles search-less setups internally.
|
|
43
|
+
*/
|
|
44
|
+
export function createSearchAPI(
|
|
45
|
+
_db: DatabaseAdapter,
|
|
46
|
+
embedding: EmbeddingProvider,
|
|
47
|
+
config: ResolvedConfig,
|
|
48
|
+
registry: PluginRegistry,
|
|
49
|
+
kvService: KVService,
|
|
50
|
+
sharedHnsw: Map<string, { hnsw: HNSWIndex; vecCache: Map<number, Float32Array> }>,
|
|
51
|
+
): SearchAPI {
|
|
52
|
+
const strategies = new Map<string, DomainVectorSearch>();
|
|
53
|
+
|
|
54
|
+
for (const mod of registry.all) {
|
|
55
|
+
if (isVectorSearchPlugin(mod)) {
|
|
56
|
+
const vs = mod.createVectorSearch();
|
|
57
|
+
if (vs) {
|
|
58
|
+
// Use full plugin name (e.g. code:servicehub-backend)
|
|
59
|
+
// so each repo gets its own vector search strategy
|
|
60
|
+
strategies.set(mod.name, vs);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const search = strategies.size > 0
|
|
66
|
+
? new CompositeVectorSearch({
|
|
67
|
+
strategies,
|
|
68
|
+
embedding,
|
|
69
|
+
})
|
|
70
|
+
: undefined;
|
|
71
|
+
|
|
72
|
+
const bm25 = new CompositeBM25Search(registry);
|
|
73
|
+
|
|
74
|
+
const contextBuilder = new ContextBuilder(search, registry, config.pruner, embedding, config.contextFields ?? {}, config.expander);
|
|
75
|
+
|
|
76
|
+
return new SearchAPI({
|
|
77
|
+
search, bm25, registry, config,
|
|
78
|
+
kvService, contextBuilder, embedding,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
export class SearchAPI {
|
|
84
|
+
constructor(private _d: SearchAPIDeps) {}
|
|
85
|
+
|
|
86
|
+
/** Build formatted context block for LLM injection. */
|
|
87
|
+
async getContext(task: string, options: ContextOptions = {}): Promise<string> {
|
|
88
|
+
if (!this._d.contextBuilder) return '';
|
|
89
|
+
return this._d.contextBuilder.build(task, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Semantic search across all loaded modules. */
|
|
93
|
+
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
94
|
+
const t0 = Date.now();
|
|
95
|
+
const lists: SearchResult[][] = [];
|
|
96
|
+
|
|
97
|
+
if (this._d.search) {
|
|
98
|
+
lists.push(await this._d.search.search(query, options));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
lists.push(...await this._collectSearchablePlugins(query, options));
|
|
102
|
+
|
|
103
|
+
let results: SearchResult[];
|
|
104
|
+
if (lists.length === 0) results = [];
|
|
105
|
+
else if (lists.length === 1) results = lists[0];
|
|
106
|
+
else results = reciprocalRankFusion(lists);
|
|
107
|
+
|
|
108
|
+
results = filterByPath(results, options?.pathPrefix);
|
|
109
|
+
this._logSearch('search', query, options, results, Date.now() - t0);
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Hybrid search: vector + BM25 → RRF. */
|
|
114
|
+
async hybridSearch(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
115
|
+
const t0 = Date.now();
|
|
116
|
+
const src = options?.sources ?? {};
|
|
117
|
+
const lists: SearchResult[][] = [];
|
|
118
|
+
|
|
119
|
+
if (this._d.search) {
|
|
120
|
+
const [vec, kw] = await Promise.all([
|
|
121
|
+
this._d.search.search(query, options),
|
|
122
|
+
Promise.resolve(this._d.bm25?.search(query, options) ?? []),
|
|
123
|
+
]);
|
|
124
|
+
lists.push(vec, kw);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
lists.push(...await this._collectSearchablePlugins(query, options));
|
|
128
|
+
lists.push(...await this._collectKvCollections(query, src));
|
|
129
|
+
|
|
130
|
+
let results: SearchResult[];
|
|
131
|
+
if (lists.length === 0) results = [];
|
|
132
|
+
else {
|
|
133
|
+
results = reciprocalRankFusion(lists);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
results = filterByPath(results, options?.pathPrefix);
|
|
137
|
+
this._logSearch('hybridSearch', query, options, results, Date.now() - t0);
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** BM25 keyword search only. */
|
|
142
|
+
async searchBM25(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
143
|
+
const t0 = Date.now();
|
|
144
|
+
let results = await this._d.bm25?.search(query, options) ?? [];
|
|
145
|
+
results = filterByPath(results, options?.pathPrefix);
|
|
146
|
+
this._logSearch('searchBM25', query, options, results, Date.now() - t0);
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Rebuild FTS5 indices. */
|
|
151
|
+
rebuildFTS(): void {
|
|
152
|
+
this._d.bm25?.rebuild?.();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Collect results from all SearchablePlugins (docs, custom). */
|
|
156
|
+
private async _collectSearchablePlugins(
|
|
157
|
+
query: string, options?: SearchOptions,
|
|
158
|
+
): Promise<SearchResult[][]> {
|
|
159
|
+
const lists: SearchResult[][] = [];
|
|
160
|
+
for (const mod of this._d.registry.all) {
|
|
161
|
+
if (!isSearchable(mod)) continue;
|
|
162
|
+
// Skip plugins that already participate via VectorSearchPlugin
|
|
163
|
+
if (isVectorSearchPlugin(mod)) continue;
|
|
164
|
+
const hits = await mod.search(query, options ? { ...options } : undefined);
|
|
165
|
+
if (hits.length > 0) lists.push(hits);
|
|
166
|
+
}
|
|
167
|
+
return lists;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Collect results from KV collections named in sources. */
|
|
171
|
+
private async _collectKvCollections(
|
|
172
|
+
query: string, sources: Record<string, number>,
|
|
173
|
+
): Promise<SearchResult[][]> {
|
|
174
|
+
const pluginNames = new Set(this._d.registry.names.map(n => n.split(':')[0]));
|
|
175
|
+
const lists: SearchResult[][] = [];
|
|
176
|
+
for (const [name, k] of Object.entries(sources)) {
|
|
177
|
+
if (pluginNames.has(name)) continue;
|
|
178
|
+
const hits = await this._d.kvService.collection(name).searchAsResults(query, k);
|
|
179
|
+
if (hits.length > 0) lists.push(hits);
|
|
180
|
+
}
|
|
181
|
+
return lists;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Log a search/hybridSearch/searchBM25 call. */
|
|
185
|
+
private _logSearch(
|
|
186
|
+
method: 'search' | 'hybridSearch' | 'searchBM25',
|
|
187
|
+
query: string,
|
|
188
|
+
options: SearchOptions | undefined,
|
|
189
|
+
results: SearchResult[],
|
|
190
|
+
durationMs: number,
|
|
191
|
+
): void {
|
|
192
|
+
logQuery({
|
|
193
|
+
source: options?.source ?? 'api',
|
|
194
|
+
method,
|
|
195
|
+
query,
|
|
196
|
+
embedding: providerKey(this._d.embedding),
|
|
197
|
+
pruner: null,
|
|
198
|
+
options: {
|
|
199
|
+
sources: options?.sources,
|
|
200
|
+
minScore: options?.minScore,
|
|
201
|
+
},
|
|
202
|
+
results: results.map(_toLogResult),
|
|
203
|
+
durationMs,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Helpers ──────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
function _toLogResult(r: SearchResult): QueryLogResult {
|
|
211
|
+
const meta = r.metadata as Record<string, unknown> | undefined;
|
|
212
|
+
return {
|
|
213
|
+
filePath: r.filePath ?? 'unknown',
|
|
214
|
+
score: r.score,
|
|
215
|
+
type: r.type,
|
|
216
|
+
name: (meta?.name as string | undefined) ?? undefined,
|
|
217
|
+
};
|
|
218
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainBank — Public API
|
|
3
|
+
*
|
|
4
|
+
* Semantic knowledge bank for AI agents.
|
|
5
|
+
*
|
|
6
|
+
* import { BrainBank } from 'brainbank';
|
|
7
|
+
* import { code } from '@brainbank/code';
|
|
8
|
+
* import { docs } from '@brainbank/docs';
|
|
9
|
+
*
|
|
10
|
+
* const brain = new BrainBank()
|
|
11
|
+
* .use(code({ repoPath: '.' }))
|
|
12
|
+
* .use(docs());
|
|
13
|
+
*
|
|
14
|
+
* // Dynamic collections — universal data primitive
|
|
15
|
+
* const errors = brain.collection('debug_errors');
|
|
16
|
+
* await errors.add('Fixed null check', { file: 'api.ts' });
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export { BrainBank } from './brainbank.ts';
|
|
21
|
+
|
|
22
|
+
// Plugin types + capability interfaces
|
|
23
|
+
export type {
|
|
24
|
+
Plugin, PluginContext,
|
|
25
|
+
IndexablePlugin, SearchablePlugin, WatchablePlugin,
|
|
26
|
+
CoEditPlugin,
|
|
27
|
+
ReembeddablePlugin, ReembedTable,
|
|
28
|
+
DocsPlugin,
|
|
29
|
+
VectorSearchPlugin, ContextFormatterPlugin, ContextFieldPlugin,
|
|
30
|
+
ContextFieldDef,
|
|
31
|
+
MigratablePlugin, BM25SearchPlugin, ExpandablePlugin,
|
|
32
|
+
FileResolvablePlugin,
|
|
33
|
+
PluginScanInfo, PluginPreviewLine,
|
|
34
|
+
} from './plugin.ts';
|
|
35
|
+
export {
|
|
36
|
+
isIndexable, isSearchable, isWatchable,
|
|
37
|
+
isDocsPlugin, isCoEditPlugin, isReembeddable,
|
|
38
|
+
isVectorSearchPlugin, isContextFormatterPlugin, isContextFieldPlugin,
|
|
39
|
+
isMigratable, isBM25SearchPlugin, isExpandablePlugin,
|
|
40
|
+
isFileResolvable,
|
|
41
|
+
} from './plugin.ts';
|
|
42
|
+
|
|
43
|
+
// Constants (core-only)
|
|
44
|
+
export { HNSW } from './constants.ts';
|
|
45
|
+
export type { HnswKey } from './constants.ts';
|
|
46
|
+
|
|
47
|
+
// Collections
|
|
48
|
+
export { Collection } from './services/collection.ts';
|
|
49
|
+
export type { CollectionItem, CollectionSearchOptions, CollectionAddOptions } from './services/collection.ts';
|
|
50
|
+
|
|
51
|
+
// Types
|
|
52
|
+
export type {
|
|
53
|
+
BrainBankConfig, ResolvedConfig,
|
|
54
|
+
EmbeddingProvider,
|
|
55
|
+
Pruner, PrunerItem,
|
|
56
|
+
Expander, ExpanderManifestItem, ExpanderResult,
|
|
57
|
+
VectorIndex, SearchHit,
|
|
58
|
+
CodeChunk,
|
|
59
|
+
GitCommitRecord,
|
|
60
|
+
SearchResult, SearchResultType,
|
|
61
|
+
CodeResult, CommitResult, DocumentResult, CollectionResult,
|
|
62
|
+
CodeResultMetadata, CommitResultMetadata, DocumentResultMetadata,
|
|
63
|
+
ContextOptions,
|
|
64
|
+
IndexResult,
|
|
65
|
+
ProgressCallback, StageProgressCallback,
|
|
66
|
+
CoEditSuggestion,
|
|
67
|
+
DocumentCollection, DocChunk,
|
|
68
|
+
WatchEvent, WatchEventHandler, WatchHandle, WatchConfig,
|
|
69
|
+
} from './types.ts';
|
|
70
|
+
export {
|
|
71
|
+
isCodeResult, isCommitResult, isDocumentResult,
|
|
72
|
+
isCollectionResult,
|
|
73
|
+
matchResult,
|
|
74
|
+
} from './types.ts';
|
|
75
|
+
|
|
76
|
+
// Embeddings
|
|
77
|
+
export { LocalEmbedding } from './providers/embeddings/local-embedding.ts';
|
|
78
|
+
export { OpenAIEmbedding } from './providers/embeddings/openai-embedding.ts';
|
|
79
|
+
export type { OpenAIEmbeddingOptions } from './providers/embeddings/openai-embedding.ts';
|
|
80
|
+
export { PerplexityEmbedding } from './providers/embeddings/perplexity-embedding.ts';
|
|
81
|
+
export type { PerplexityEmbeddingOptions } from './providers/embeddings/perplexity-embedding.ts';
|
|
82
|
+
export { PerplexityContextEmbedding } from './providers/embeddings/perplexity-context-embedding.ts';
|
|
83
|
+
export type { PerplexityContextEmbeddingOptions } from './providers/embeddings/perplexity-context-embedding.ts';
|
|
84
|
+
export { EmbeddingWorkerProxy } from './providers/embeddings/embedding-worker.ts';
|
|
85
|
+
export type { ReembedResult, ReembedOptions } from './engine/reembed.ts';
|
|
86
|
+
export type { WatchOptions } from './services/watch.ts';
|
|
87
|
+
export { Watcher } from './services/watch.ts';
|
|
88
|
+
export { WebhookServer } from './services/webhook-server.ts';
|
|
89
|
+
export type { WebhookHandler } from './services/webhook-server.ts';
|
|
90
|
+
export { HttpServer } from './services/http-server.ts';
|
|
91
|
+
export type { HttpServerOptions } from './services/http-server.ts';
|
|
92
|
+
export { isServerRunning, getServerUrl, readPid, writePid, removePid, DEFAULT_PORT } from './services/daemon.ts';
|
|
93
|
+
|
|
94
|
+
// Pruner
|
|
95
|
+
export { HaikuPruner } from './providers/pruners/haiku-pruner.ts';
|
|
96
|
+
export type { HaikuPrunerOptions } from './providers/pruners/haiku-pruner.ts';
|
|
97
|
+
export { pruneResults } from './lib/prune.ts';
|
|
98
|
+
|
|
99
|
+
// Expander
|
|
100
|
+
export { HaikuExpander } from './providers/pruners/haiku-expander.ts';
|
|
101
|
+
export type { HaikuExpanderOptions } from './providers/pruners/haiku-expander.ts';
|
|
102
|
+
|
|
103
|
+
// Embedding resolver
|
|
104
|
+
export { resolveEmbedding, providerKey } from './providers/embeddings/resolve.ts';
|
|
105
|
+
export type { EmbeddingKey } from './providers/embeddings/resolve.ts';
|
|
106
|
+
|
|
107
|
+
// Config
|
|
108
|
+
export { resolveConfig, DEFAULTS } from './config.ts';
|
|
109
|
+
|
|
110
|
+
// Migrations
|
|
111
|
+
export type { Migration } from './db/migrations.ts';
|
|
112
|
+
export { runPluginMigrations } from './db/migrations.ts';
|
|
113
|
+
|
|
114
|
+
// Incremental indexing tracker
|
|
115
|
+
export type { IncrementalTracker } from './db/tracker.ts';
|
|
116
|
+
export { createTracker } from './db/tracker.ts';
|
|
117
|
+
|
|
118
|
+
// Vector indices
|
|
119
|
+
export { HNSWIndex } from './providers/vector/hnsw-index.ts';
|
|
120
|
+
export { searchMMR } from './search/vector/mmr.ts';
|
|
121
|
+
|
|
122
|
+
// Language support (used by @brainbank/code)
|
|
123
|
+
export { SUPPORTED_EXTENSIONS, IGNORE_DIRS, isSupported, getLanguage, isIgnoredDir, isIgnoredFile } from './lib/languages.ts';
|
|
124
|
+
|
|
125
|
+
// Math utilities (needed by plugins)
|
|
126
|
+
export { vecToBuffer, cosineSimilarity, normalize } from './lib/math.ts';
|
|
127
|
+
|
|
128
|
+
// KV service (collection infrastructure)
|
|
129
|
+
export { KVService } from './services/kv-service.ts';
|
|
130
|
+
|
|
131
|
+
// Search internals (plugins may use these)
|
|
132
|
+
export { ContextBuilder } from './search/context-builder.ts';
|
|
133
|
+
export { CompositeVectorSearch } from './search/vector/composite-vector-search.ts';
|
|
134
|
+
export { CompositeBM25Search } from './search/keyword/composite-bm25-search.ts';
|
|
135
|
+
export { reciprocalRankFusion } from './lib/rrf.ts';
|
|
136
|
+
export { sanitizeFTS, normalizeBM25, escapeLike } from './lib/fts.ts';
|
|
137
|
+
|
|
138
|
+
// Search types
|
|
139
|
+
export type { SearchStrategy, SearchOptions, DomainVectorSearch } from './search/types.ts';
|
|
140
|
+
|
|
141
|
+
// Multi-process coordination
|
|
142
|
+
export { bumpVersion, getVersions, getVersion } from './db/metadata.ts';
|
|
143
|
+
export { acquireLock, releaseLock, withLock } from './lib/write-lock.ts';
|
|
144
|
+
|
|
145
|
+
// Database adapter (for plugin access and custom adapters)
|
|
146
|
+
export type { DatabaseAdapter, PreparedStatement, ExecuteResult, AdapterCapabilities } from './db/adapter.ts';
|
|
147
|
+
export type { KvDataRow, KvVectorRow, EmbeddingMetaRow, VectorRow, CountRow } from './db/adapter.ts';
|
|
148
|
+
export { SQLiteAdapter } from './db/sqlite-adapter.ts';
|
|
149
|
+
|
|
150
|
+
// Factory (for programmatic BrainBank creation)
|
|
151
|
+
export { createBrain, resetFactoryCache, contextFromCLI } from './cli/factory/index.ts';
|
|
152
|
+
export type { BrainContext } from './cli/factory/brain-context.ts';
|
|
153
|
+
export type { ProjectConfig } from './cli/factory/config-loader.ts';
|
|
154
|
+
|
package/src/lib/fts.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainBank — FTS Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for SQLite FTS5 query sanitization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Split camelCase, PascalCase, and snake_case into individual words.
|
|
9
|
+
* "MagicLinkCallback" → "Magic Link Callback"
|
|
10
|
+
* "tenant_worker" → "tenant worker"
|
|
11
|
+
*/
|
|
12
|
+
function splitCompound(word: string): string {
|
|
13
|
+
return word
|
|
14
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case
|
|
15
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // HTMLParser → HTML Parser
|
|
16
|
+
.replace(/[_\-./\\]/g, ' ') // snake_case, kebab-case, paths
|
|
17
|
+
.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sanitize a user query for FTS5 syntax.
|
|
22
|
+
* Strips operators that would cause parse errors, splits compound words,
|
|
23
|
+
* and converts words to implicit AND with exact-match quoting.
|
|
24
|
+
*/
|
|
25
|
+
export function sanitizeFTS(query: string): string {
|
|
26
|
+
const clean = query
|
|
27
|
+
.replace(/[{}[\]()^~*:]/g, ' ')
|
|
28
|
+
.replace(/\bAND\b|\bOR\b|\bNOT\b|\bNEAR\b/gi, '')
|
|
29
|
+
.trim();
|
|
30
|
+
|
|
31
|
+
// Split compound words (camelCase, PascalCase, snake_case)
|
|
32
|
+
const expanded = clean.split(/\s+/)
|
|
33
|
+
.map(w => splitCompound(w))
|
|
34
|
+
.join(' ');
|
|
35
|
+
|
|
36
|
+
const words = expanded.split(/\s+/).filter(w => w.length > 1);
|
|
37
|
+
if (words.length === 0) return '';
|
|
38
|
+
|
|
39
|
+
return words.map(w => `"${w}"`).join(' ');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalize BM25 score from SQLite (negative, lower = better)
|
|
44
|
+
* to 0.0–1.0 (higher = better) for consistency with vector search.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeBM25(rawScore: number): number {
|
|
47
|
+
const abs = Math.abs(rawScore);
|
|
48
|
+
return 1.0 / (1.0 + Math.exp(-0.3 * (abs - 5)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Escape SQL LIKE wildcard characters (`%`, `_`, `\`).
|
|
53
|
+
* Use with `LIKE ? ESCAPE '\'` in queries.
|
|
54
|
+
*/
|
|
55
|
+
export function escapeLike(s: string): string {
|
|
56
|
+
return s.replace(/[%_\\]/g, '\\$&');
|
|
57
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainBank — Language Registry
|
|
3
|
+
*
|
|
4
|
+
* Supported file extensions, language mappings, and ignore lists.
|
|
5
|
+
* Controls which files get indexed and how they're chunked.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const SUPPORTED_EXTENSIONS: Record<string, string> = {
|
|
10
|
+
// TypeScript / JavaScript
|
|
11
|
+
'.ts': 'typescript',
|
|
12
|
+
'.tsx': 'typescript',
|
|
13
|
+
'.js': 'javascript',
|
|
14
|
+
'.jsx': 'javascript',
|
|
15
|
+
'.mjs': 'javascript',
|
|
16
|
+
'.cjs': 'javascript',
|
|
17
|
+
|
|
18
|
+
// Systems
|
|
19
|
+
'.go': 'go',
|
|
20
|
+
'.rs': 'rust',
|
|
21
|
+
'.cpp': 'cpp',
|
|
22
|
+
'.cc': 'cpp',
|
|
23
|
+
'.c': 'c',
|
|
24
|
+
'.h': 'c',
|
|
25
|
+
'.hpp': 'cpp',
|
|
26
|
+
|
|
27
|
+
// JVM
|
|
28
|
+
'.java': 'java',
|
|
29
|
+
'.kt': 'kotlin',
|
|
30
|
+
'.scala': 'scala',
|
|
31
|
+
|
|
32
|
+
// Scripting
|
|
33
|
+
'.py': 'python',
|
|
34
|
+
'.rb': 'ruby',
|
|
35
|
+
'.php': 'php',
|
|
36
|
+
'.lua': 'lua',
|
|
37
|
+
'.sh': 'bash',
|
|
38
|
+
'.bash': 'bash',
|
|
39
|
+
'.zsh': 'bash',
|
|
40
|
+
|
|
41
|
+
// Web
|
|
42
|
+
'.html': 'html',
|
|
43
|
+
'.css': 'css',
|
|
44
|
+
'.scss': 'scss',
|
|
45
|
+
'.less': 'less',
|
|
46
|
+
'.svelte': 'svelte',
|
|
47
|
+
'.vue': 'vue',
|
|
48
|
+
|
|
49
|
+
// Data / Config (JSON + YAML excluded — config/CI files cause search noise)
|
|
50
|
+
'.toml': 'toml',
|
|
51
|
+
'.xml': 'xml',
|
|
52
|
+
'.graphql': 'graphql',
|
|
53
|
+
'.gql': 'graphql',
|
|
54
|
+
|
|
55
|
+
// Database
|
|
56
|
+
'.sql': 'sql',
|
|
57
|
+
'.prisma': 'prisma',
|
|
58
|
+
|
|
59
|
+
// Other
|
|
60
|
+
'.swift': 'swift',
|
|
61
|
+
'.dart': 'dart',
|
|
62
|
+
'.r': 'r',
|
|
63
|
+
'.ex': 'elixir',
|
|
64
|
+
'.exs': 'elixir',
|
|
65
|
+
'.erl': 'erlang',
|
|
66
|
+
'.zig': 'zig',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
export const IGNORE_DIRS = new Set([
|
|
71
|
+
// Package managers
|
|
72
|
+
'node_modules',
|
|
73
|
+
'bower_components',
|
|
74
|
+
'.pnpm',
|
|
75
|
+
|
|
76
|
+
// Build output
|
|
77
|
+
'dist',
|
|
78
|
+
'build',
|
|
79
|
+
'out',
|
|
80
|
+
'.next',
|
|
81
|
+
'.nuxt',
|
|
82
|
+
'.output',
|
|
83
|
+
'.svelte-kit',
|
|
84
|
+
|
|
85
|
+
// Auto-generated code
|
|
86
|
+
'generated',
|
|
87
|
+
'sdk',
|
|
88
|
+
'openapi',
|
|
89
|
+
|
|
90
|
+
// Version control
|
|
91
|
+
'.git',
|
|
92
|
+
'.hg',
|
|
93
|
+
'.svn',
|
|
94
|
+
|
|
95
|
+
// IDE / Editor
|
|
96
|
+
'.idea',
|
|
97
|
+
'.vscode',
|
|
98
|
+
|
|
99
|
+
// Runtime / Cache
|
|
100
|
+
'__pycache__',
|
|
101
|
+
'.pytest_cache',
|
|
102
|
+
'venv',
|
|
103
|
+
'.venv',
|
|
104
|
+
'.env',
|
|
105
|
+
'.tox',
|
|
106
|
+
|
|
107
|
+
// Coverage / Test artifacts
|
|
108
|
+
'coverage',
|
|
109
|
+
'.nyc_output',
|
|
110
|
+
'htmlcov',
|
|
111
|
+
|
|
112
|
+
// Compiled
|
|
113
|
+
'target', // Rust, Java
|
|
114
|
+
'.cargo',
|
|
115
|
+
'vendor', // Go, PHP
|
|
116
|
+
|
|
117
|
+
// Database (auto-generated migrations, dumps, seeds)
|
|
118
|
+
'migrations',
|
|
119
|
+
'db_dumps',
|
|
120
|
+
'seeds',
|
|
121
|
+
|
|
122
|
+
// AI / Model cache
|
|
123
|
+
'.model-cache',
|
|
124
|
+
'.brainbank',
|
|
125
|
+
|
|
126
|
+
// OS
|
|
127
|
+
'.DS_Store',
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
export const IGNORE_FILES = new Set([
|
|
132
|
+
'package-lock.json',
|
|
133
|
+
'yarn.lock',
|
|
134
|
+
'pnpm-lock.yaml',
|
|
135
|
+
'bun.lockb',
|
|
136
|
+
'Cargo.lock',
|
|
137
|
+
'Gemfile.lock',
|
|
138
|
+
'poetry.lock',
|
|
139
|
+
'composer.lock',
|
|
140
|
+
'go.sum',
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
import path from 'node:path';
|
|
145
|
+
|
|
146
|
+
/** Check if a file extension is supported for indexing. */
|
|
147
|
+
export function isSupported(filePath: string): boolean {
|
|
148
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
149
|
+
return ext in SUPPORTED_EXTENSIONS;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Get the language name for a file. Returns undefined if not supported. */
|
|
153
|
+
export function getLanguage(filePath: string): string | undefined {
|
|
154
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
155
|
+
return SUPPORTED_EXTENSIONS[ext];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Check if a directory name should be ignored. */
|
|
159
|
+
export function isIgnoredDir(dirName: string): boolean {
|
|
160
|
+
return IGNORE_DIRS.has(dirName);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Check if a filename should be ignored. */
|
|
164
|
+
export function isIgnoredFile(fileName: string): boolean {
|
|
165
|
+
return IGNORE_FILES.has(fileName);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Check if a relative path matches any of the given glob patterns. */
|
|
169
|
+
export function matchesGlob(relPath: string, patterns: string[]): boolean {
|
|
170
|
+
for (const pattern of patterns) {
|
|
171
|
+
const regex = pattern
|
|
172
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex specials (not * ?)
|
|
173
|
+
.replace(/\*\*/g, '\x00') // placeholder for **
|
|
174
|
+
.replace(/\*/g, '[^/]*') // * = anything except /
|
|
175
|
+
.replace(/\?/g, '.') // ? = any single char
|
|
176
|
+
.replace(/\x00/g, '.*'); // ** = anything including /
|
|
177
|
+
if (new RegExp(`^${regex}$`).test(relPath)) return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|