brainbank 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -1398
- package/bin/brainbank +5 -1
- package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
- package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
- package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
- package/dist/chunk-3JZIM5AU.js.map +1 -0
- package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
- package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
- package/dist/chunk-7JDCHUJV.js +89 -0
- package/dist/chunk-7JDCHUJV.js.map +1 -0
- package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
- package/dist/chunk-7T2ZCZQA.js.map +1 -0
- package/dist/chunk-E3J37GDA.js +74 -0
- package/dist/chunk-E3J37GDA.js.map +1 -0
- package/dist/chunk-JEFWMS5Z.js +217 -0
- package/dist/chunk-JEFWMS5Z.js.map +1 -0
- package/dist/chunk-JRVYSTMP.js +3256 -0
- package/dist/chunk-JRVYSTMP.js.map +1 -0
- package/dist/chunk-OLDHLOMT.js +69 -0
- package/dist/chunk-OLDHLOMT.js.map +1 -0
- package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
- package/dist/chunk-QTZNB6AK.js.map +1 -0
- package/dist/chunk-RFF7HMP6.js +109 -0
- package/dist/chunk-RFF7HMP6.js.map +1 -0
- package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
- package/dist/chunk-TD3TEFI3.js.map +1 -0
- package/dist/cli.js +1036 -341
- package/dist/cli.js.map +1 -1
- package/dist/haiku-expander-WOVJIVXD.js +8 -0
- package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
- package/dist/http-server-GIRELCCL.js +9 -0
- package/dist/index.d.ts +1774 -611
- package/dist/index.js +282 -70
- package/dist/index.js.map +1 -1
- package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
- package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
- package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
- package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
- package/dist/plugin-FF4Q34TI.js +32 -0
- package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
- package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
- package/package.json +25 -52
- package/src/brainbank.ts +620 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +171 -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 +72 -0
- package/src/cli/commands/index.ts +282 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp.ts +13 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +365 -0
- package/src/cli/commands/search.ts +130 -0
- package/src/cli/commands/stats.ts +44 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +43 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +123 -0
- package/src/cli/factory/config-loader.ts +72 -0
- package/src/cli/factory/index.ts +65 -0
- package/src/cli/factory/plugin-loader.ts +146 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/server-client.ts +135 -0
- package/src/cli/utils.ts +121 -0
- package/src/config.ts +50 -0
- package/src/constants.ts +13 -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 +208 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +85 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +222 -0
- package/src/index.ts +159 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +125 -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/rerank.ts +33 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/plugin.ts +323 -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 +152 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/rerankers/qwen3-reranker.ts +180 -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 +61 -0
- package/src/search/context-builder.ts +298 -0
- package/src/search/keyword/composite-bm25-search.ts +62 -0
- package/src/search/types.ts +35 -0
- package/src/search/vector/composite-vector-search.ts +76 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +405 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +288 -0
- package/src/services/kv-service.ts +65 -0
- package/src/services/plugin-registry.ts +109 -0
- package/src/services/watch.ts +348 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +504 -0
- package/dist/base-3SNc_CeY.d.ts +0 -593
- package/dist/chunk-424UFCY7.js.map +0 -1
- package/dist/chunk-7EZR47JV.js +0 -232
- package/dist/chunk-7EZR47JV.js.map +0 -1
- package/dist/chunk-B77KABWH.js.map +0 -1
- package/dist/chunk-CCXVL56V.js.map +0 -1
- package/dist/chunk-DI3H6JVZ.js +0 -2432
- package/dist/chunk-DI3H6JVZ.js.map +0 -1
- package/dist/chunk-FGL32LUJ.js +0 -754
- package/dist/chunk-FGL32LUJ.js.map +0 -1
- package/dist/chunk-JRSKWF6K.js +0 -313
- package/dist/chunk-JRSKWF6K.js.map +0 -1
- package/dist/chunk-U2Q2XGPZ.js +0 -42
- package/dist/chunk-U2Q2XGPZ.js.map +0 -1
- package/dist/chunk-VQ27YUHH.js +0 -629
- package/dist/chunk-VQ27YUHH.js.map +0 -1
- package/dist/chunk-VVXYZIIB.js +0 -304
- package/dist/chunk-VVXYZIIB.js.map +0 -1
- package/dist/chunk-YOLKSYWK.js +0 -79
- package/dist/chunk-YOLKSYWK.js.map +0 -1
- package/dist/chunk-ZNLN2VWV.js.map +0 -1
- package/dist/code.d.ts +0 -33
- package/dist/code.js +0 -9
- package/dist/docs.d.ts +0 -21
- package/dist/docs.js +0 -9
- package/dist/git.d.ts +0 -33
- package/dist/git.js +0 -9
- package/dist/memory.d.ts +0 -17
- package/dist/memory.js +0 -9
- package/dist/notes.d.ts +0 -17
- package/dist/notes.js +0 -10
- package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
- package/dist/resolve-CUJWY6HP.js.map +0 -1
- /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
- /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
- /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
- /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
- /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
- /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
- /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
- /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
- /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
- /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/indexers/docs/docs-indexer.ts","../src/search/vector/rerank.ts","../src/indexers/docs/document-search.ts","../src/indexers/docs/docs-plugin.ts"],"sourcesContent":["/**\n * BrainBank — Document Indexer\n * \n * Indexes generic document collections (markdown, text, etc.)\n * with heading-aware smart chunking, inspired by qmd.\n * \n * const indexer = new DocsIndexer(db, embedding, hnsw, vecCache);\n * await indexer.indexCollection('notes', '/path/to/notes', '**\\/*.md');\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport type { Database } from '@/db/database.ts';\nimport type { EmbeddingProvider, VectorIndex } from '@/types.ts';\nimport type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';\n\n// ── Break Point Scoring (qmd-inspired) ──────────────\n\ninterface BreakPoint {\n pos: number; // character position\n score: number; // break quality (higher = better)\n}\n\nconst BREAK_SCORES: [RegExp, number][] = [\n [/^# /, 100], // H1\n [/^## /, 90], // H2\n [/^### /, 80], // H3\n [/^#### /, 70], // H4\n [/^##### /, 60], // H5\n [/^###### /, 50], // H6\n [/^```/, 80], // Code fence\n [/^---$/, 60], // Horizontal rule\n [/^\\*\\*\\*$/, 60], // Horizontal rule alt\n [/^$/, 20], // Blank line (paragraph break)\n [/^[-*+] /, 5], // List item\n];\n\n// ── Chunk Target ────────────────────────────────────\n\nconst TARGET_CHARS = 3000; // ~900 tokens\nconst WINDOW_CHARS = 600; // search window before cutoff\nconst MIN_CHUNK_CHARS = 200; // don't create tiny chunks\n\n\n/** Ignored output/vendor directories when walking docs. */\nconst IGNORED_DOC_DIRS = new Set([\n 'node_modules', '.git', '.hg', '.svn',\n 'dist', 'build', 'out', 'coverage', '.next',\n '__pycache__', '.tox', '.venv', 'venv',\n 'vendor', 'target', '.cache', '.turbo',\n]);\n\n// ── DocsIndexer ──────────────────────────────────────\n\nexport class DocsIndexer {\n constructor(\n private _db: Database,\n private _embedding: EmbeddingProvider,\n private _hnsw: HNSWIndex,\n private _vecCache: Map<number, Float32Array>,\n ) {}\n\n /**\n * Index all documents in a collection.\n * Incremental — skips unchanged files (by content hash).\n */\n async indexCollection(\n collection: string,\n dirPath: string,\n pattern: string = '**/*.md',\n options: {\n ignore?: string[];\n onProgress?: (file: string, current: number, total: number) => void;\n } = {},\n ): Promise<{ indexed: number; skipped: number; chunks: number }> {\n const absDir = path.resolve(dirPath);\n if (!fs.existsSync(absDir)) {\n throw new Error(`Collection path does not exist: ${absDir}`);\n }\n\n const files = this._walkFiles(absDir, pattern, options.ignore);\n let indexed = 0, skipped = 0, totalChunks = 0;\n\n for (let i = 0; i < files.length; i++) {\n const relPath = files[i];\n options.onProgress?.(relPath, i + 1, files.length);\n\n const absPath = path.join(absDir, relPath);\n const content = fs.readFileSync(absPath, 'utf-8');\n const hash = createHash('sha256').update(content).digest('hex').slice(0, 16);\n\n if (this._isUnchanged(collection, relPath, hash)) {\n skipped++;\n continue;\n }\n\n this._removeOldChunks(collection, relPath);\n const chunkCount = await this._indexFile(collection, relPath, content, hash);\n indexed++;\n totalChunks += chunkCount;\n }\n\n return { indexed, skipped, chunks: totalChunks };\n }\n\n /** Walk directory tree and collect matching files. */\n private _walkFiles(absDir: string, pattern: string, ignore?: string[]): string[] {\n const patternExt = pattern.match(/\\.([\\w]+)$/)?.[1];\n const files: string[] = [];\n\n const walk = (dir: string, base: string): void => {\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); }\n catch { return; }\n for (const e of entries) {\n const rel = base ? `${base}/${e.name}` : e.name;\n if (e.isDirectory()) {\n if (IGNORED_DOC_DIRS.has(e.name)) continue;\n walk(path.join(dir, e.name), rel);\n } else if (e.isFile()) {\n if (this._isIgnoredFile(rel, ignore)) continue;\n const ext = path.extname(e.name).slice(1);\n if (!patternExt || ext === patternExt) files.push(rel);\n }\n }\n };\n walk(absDir, '');\n return files;\n }\n\n /** Check if a file matches any ignore patterns. */\n private _isIgnoredFile(relPath: string, ignore?: string[]): boolean {\n if (!ignore) return false;\n return ignore.some(ig => {\n const regex = ig\n .replace(/\\*\\*/g, '{{GLOBSTAR}}')\n .replace(/\\*/g, '{{STAR}}')\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\{\\{GLOBSTAR\\}\\}/g, '.*')\n .replace(/\\{\\{STAR\\}\\}/g, '[^/]*');\n return new RegExp(regex).test(relPath);\n });\n }\n\n /** Check if all chunks for a file match the current hash and have vectors. */\n private _isUnchanged(collection: string, relPath: string, hash: string): boolean {\n const existing = this._db.prepare(\n `SELECT dc.id, dc.content_hash, dv.chunk_id AS has_vector\n FROM doc_chunks dc\n LEFT JOIN doc_vectors dv ON dv.chunk_id = dc.id\n WHERE dc.collection = ? AND dc.file_path = ?`\n ).all(collection, relPath) as any[];\n\n return existing.length > 0 &&\n existing.every((c: any) => c.content_hash === hash && c.has_vector != null);\n }\n\n /** Remove old chunks and their HNSW vectors for a file. */\n private _removeOldChunks(collection: string, relPath: string): void {\n const oldChunks = this._db.prepare(\n 'SELECT id FROM doc_chunks WHERE collection = ? AND file_path = ?'\n ).all(collection, relPath) as any[];\n\n for (const old of oldChunks) {\n this._hnsw.remove(old.id);\n this._vecCache.delete(old.id);\n }\n this._db.prepare(\n 'DELETE FROM doc_chunks WHERE collection = ? AND file_path = ?'\n ).run(collection, relPath);\n }\n\n /** Index a single file: chunk, embed, store in DB + HNSW. */\n private async _indexFile(\n collection: string, relPath: string, content: string, hash: string,\n ): Promise<number> {\n const title = this._extractTitle(content, relPath);\n const chunks = this._smartChunk(content);\n\n const insertChunk = this._db.prepare(`\n INSERT INTO doc_chunks (collection, file_path, title, content, seq, pos, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n const chunkIds: number[] = [];\n this._db.transaction(() => {\n for (let seq = 0; seq < chunks.length; seq++) {\n const result = insertChunk.run(\n collection, relPath, title, chunks[seq].text, seq, chunks[seq].pos, hash,\n );\n chunkIds.push(Number(result.lastInsertRowid));\n }\n });\n\n const texts = chunks.map(c => `title: ${title} | text: ${c.text}`);\n const embeddings = await this._embedding.embedBatch(texts);\n\n const insertVec = this._db.prepare(\n 'INSERT OR REPLACE INTO doc_vectors (chunk_id, embedding) VALUES (?, ?)'\n );\n this._db.transaction(() => {\n for (let j = 0; j < chunkIds.length; j++) {\n insertVec.run(chunkIds[j], Buffer.from(embeddings[j].buffer));\n }\n });\n\n for (let j = 0; j < chunkIds.length; j++) {\n this._hnsw.add(embeddings[j], chunkIds[j]);\n this._vecCache.set(chunkIds[j], embeddings[j]);\n }\n\n return chunks.length;\n }\n\n /** Remove all indexed data for a collection. */\n removeCollection(collection: string): void {\n const chunks = this._db.prepare(\n 'SELECT id FROM doc_chunks WHERE collection = ?'\n ).all(collection) as any[];\n for (const chunk of chunks) {\n this._hnsw.remove(chunk.id);\n this._vecCache.delete(chunk.id);\n }\n\n this._db.prepare('DELETE FROM doc_chunks WHERE collection = ?').run(collection);\n this._db.prepare('DELETE FROM collections WHERE name = ?').run(collection);\n this._db.prepare('DELETE FROM path_contexts WHERE collection = ?').run(collection);\n }\n\n // ── Smart Chunking ──────────────────────────────\n\n /** Split document into chunks at natural markdown boundaries. */\n private _smartChunk(text: string): { text: string; pos: number }[] {\n if (text.length <= TARGET_CHARS) {\n return [{ text, pos: 0 }];\n }\n\n const lines = text.split('\\n');\n const breakPoints = this._findBreakPoints(lines);\n const chunks: { text: string; pos: number }[] = [];\n let chunkStart = 0;\n\n while (chunkStart < text.length) {\n const remaining = text.length - chunkStart;\n if (remaining <= TARGET_CHARS + WINDOW_CHARS) {\n this._flushRemainder(text, chunkStart, chunks);\n break;\n }\n\n const bestBreak = this._findBestBreak(chunkStart, breakPoints);\n const chunkText = text.slice(chunkStart, bestBreak).trim();\n if (chunkText.length >= MIN_CHUNK_CHARS) {\n chunks.push({ text: chunkText, pos: chunkStart });\n }\n chunkStart = bestBreak;\n }\n\n return chunks;\n }\n\n /** Handle the last chunk: merge if too small, otherwise push. */\n private _flushRemainder(\n text: string, chunkStart: number, chunks: { text: string; pos: number }[],\n ): void {\n const lastText = text.slice(chunkStart).trim();\n if (lastText.length >= MIN_CHUNK_CHARS) {\n chunks.push({ text: lastText, pos: chunkStart });\n } else if (chunks.length > 0) {\n chunks[chunks.length - 1].text += '\\n' + lastText;\n } else {\n chunks.push({ text: lastText, pos: chunkStart });\n }\n }\n\n /** Find the best break position within the target window. */\n private _findBestBreak(chunkStart: number, breakPoints: BreakPoint[]): number {\n const targetEnd = chunkStart + TARGET_CHARS;\n const windowStart = targetEnd - WINDOW_CHARS;\n\n let bestBreak = targetEnd;\n let bestScore = 0;\n\n for (const bp of breakPoints) {\n if (bp.pos <= chunkStart) continue;\n if (bp.pos > targetEnd + WINDOW_CHARS / 2) break;\n if (bp.pos < windowStart) continue;\n\n const distance = Math.abs(bp.pos - targetEnd);\n const decay = 1 - (distance / WINDOW_CHARS) ** 2 * 0.7;\n const finalScore = bp.score * decay;\n\n if (finalScore > bestScore) {\n bestScore = finalScore;\n bestBreak = bp.pos;\n }\n }\n\n return bestBreak;\n }\n\n /** Find all potential break points in the document with scores. */\n private _findBreakPoints(lines: string[]): BreakPoint[] {\n const points: BreakPoint[] = [];\n let charPos = 0;\n let inCodeBlock = false;\n\n for (const line of lines) {\n if (line.trimStart().startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n if (!inCodeBlock) {\n points.push({ pos: charPos + line.length + 1, score: 80 });\n }\n charPos += line.length + 1;\n continue;\n }\n\n if (inCodeBlock) {\n charPos += line.length + 1;\n continue;\n }\n\n for (const [pattern, score] of BREAK_SCORES) {\n if (pattern.test(line.trim())) {\n points.push({ pos: charPos, score });\n break;\n }\n }\n\n charPos += line.length + 1;\n }\n\n return points;\n }\n\n /** Extract document title from first heading or filename. */\n private _extractTitle(content: string, filePath: string): string {\n const match = content.match(/^#{1,3}\\s+(.+)$/m);\n if (match) return match[1].trim();\n return path.basename(filePath, path.extname(filePath));\n }\n}\n","/**\n * BrainBank — Rerank\n * \n * Position-aware score blending between retrieval and reranker.\n * Pure function — no state.\n * \n * Top 1-3: 75% retrieval / 25% reranker (preserves exact matches)\n * Top 4-10: 60% retrieval / 40% reranker\n * Top 11+: 40% retrieval / 60% reranker (trust reranker more)\n */\n\nimport type { Reranker, SearchResult } from '@/types.ts';\n\n/** Re-rank results using position-aware blending. */\nexport async function rerank(\n query: string,\n results: SearchResult[],\n reranker: Reranker,\n): Promise<SearchResult[]> {\n const documents = results.map(r => r.content);\n const scores = await reranker.rank(query, documents);\n\n const blended = results.map((r, i) => {\n const pos = i + 1;\n const rrfWeight = pos <= 3 ? 0.75 : pos <= 10 ? 0.60 : 0.40;\n return {\n ...r,\n score: rrfWeight * r.score + (1 - rrfWeight) * (scores[i] ?? 0),\n };\n });\n\n return blended.sort((a, b) => b.score - a.score);\n}\n","/**\n * BrainBank — Document Search\n *\n * Hybrid search (vector + BM25 → RRF) for indexed documents.\n * Extracted from DocsPlugin to keep plugin management separate from search logic.\n */\n\nimport type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';\nimport type { Database } from '@/db/database.ts';\nimport type { EmbeddingProvider, SearchResult, Reranker } from '@/types.ts';\nimport { reciprocalRankFusion } from '@/lib/rrf.ts';\nimport { rerank } from '@/search/vector/rerank.ts';\nimport { normalizeBM25 } from '@/lib/fts.ts';\n\nexport interface DocumentSearchDeps {\n db: Database;\n embedding: EmbeddingProvider;\n hnsw: HNSWIndex;\n vecCache: Map<number, Float32Array>;\n reranker?: Reranker;\n}\n\nexport class DocumentSearch {\n constructor(private _d: DocumentSearchDeps) {}\n\n /** Hybrid search (vector + BM25 → RRF), with dedup and optional reranking. */\n async search(query: string, options?: {\n collection?: string;\n k?: number;\n minScore?: number;\n mode?: 'hybrid' | 'vector' | 'keyword';\n }): Promise<SearchResult[]> {\n const k = options?.k ?? 8;\n const mode = options?.mode ?? 'hybrid';\n const minScore = options?.minScore ?? 0;\n\n if (mode === 'keyword') return this._dedup(this._searchBM25(query, k * 2, minScore, options?.collection), k);\n if (mode === 'vector') return this._dedup(await this._searchVector(query, k * 2, minScore, options?.collection), k);\n\n // Hybrid: over-fetch from both, fuse with RRF, then dedup by file\n const fetchK = k * 2;\n const [vecHits, bm25Hits] = await Promise.all([\n this._searchVector(query, fetchK, 0, options?.collection),\n Promise.resolve(this._searchBM25(query, fetchK, 0, options?.collection)),\n ]);\n\n if (vecHits.length === 0 && bm25Hits.length === 0) return [];\n if (bm25Hits.length === 0) return this._dedup(vecHits.filter(h => h.score >= minScore), k);\n if (vecHits.length === 0) return this._dedup(bm25Hits.filter(h => h.score >= minScore), k);\n\n const fused = reciprocalRankFusion([vecHits, bm25Hits]);\n\n // Map fused results back to doc SearchResults\n const allById = new Map<number, SearchResult>();\n for (const h of [...vecHits, ...bm25Hits]) {\n const id = (h.metadata as any)?.chunkId;\n if (id != null) allById.set(id, h);\n }\n\n const results: SearchResult[] = [];\n for (const r of fused) {\n const chunkId = (r.metadata as any)?.chunkId;\n const original = allById.get(chunkId);\n if (!original) continue;\n const merged = { ...original, score: r.score };\n if (merged.score >= minScore) results.push(merged);\n }\n\n const deduped = this._dedup(results, k);\n return this._rerankResults(query, deduped);\n }\n\n /** Apply reranking if a reranker is configured. */\n private async _rerankResults(query: string, results: SearchResult[]): Promise<SearchResult[]> {\n if (!this._d.reranker || results.length <= 1) return results;\n return rerank(query, results, this._d.reranker);\n }\n\n /** Deduplicate results by file path — keep best-scoring chunk per file. */\n private _dedup(results: SearchResult[], k: number): SearchResult[] {\n const seen = new Map<string, SearchResult>();\n for (const r of results) {\n const key = r.filePath ?? '';\n if (!seen.has(key) || (seen.get(key)!.score < r.score)) {\n seen.set(key, r);\n }\n }\n return [...seen.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n\n /** Vector-only search via HNSW. */\n private async _searchVector(query: string, k: number, minScore: number, collection?: string): Promise<SearchResult[]> {\n if (this._d.hnsw.size === 0) return [];\n const queryVec = await this._d.embedding.embed(query);\n\n let searchK = k;\n if (collection && this._d.hnsw.size > 0) {\n const collectionCount = (this._d.db.prepare(\n 'SELECT COUNT(*) as c FROM doc_chunks WHERE collection = ?'\n ).get(collection) as any)?.c ?? 0;\n const totalChunks = (this._d.db.prepare(\n 'SELECT COUNT(*) as c FROM doc_chunks'\n ).get() as any)?.c ?? 1;\n const ratio = collectionCount > 0\n ? Math.max(3, Math.min(50, Math.ceil(totalChunks / collectionCount)))\n : 3;\n searchK = Math.min(k * ratio, this._d.hnsw.size);\n }\n\n const hits = this._d.hnsw.search(queryVec, searchK);\n const results: SearchResult[] = [];\n\n for (const hit of hits) {\n if (minScore && hit.score < minScore) continue;\n const chunk = this._d.db.prepare('SELECT * FROM doc_chunks WHERE id = ?').get(hit.id) as any;\n if (!chunk) continue;\n if (collection && chunk.collection !== collection) continue;\n\n results.push({\n type: 'document',\n score: hit.score,\n filePath: chunk.file_path,\n content: chunk.content,\n context: this._getDocContext(chunk.collection, chunk.file_path),\n metadata: {\n collection: chunk.collection,\n title: chunk.title,\n seq: chunk.seq,\n chunkId: chunk.id,\n },\n });\n\n if (results.length >= k) break;\n }\n\n return results;\n }\n\n /** BM25 keyword search via FTS5 (OR-mode for natural language). */\n private _searchBM25(query: string, k: number, minScore: number, collection?: string): SearchResult[] {\n const ftsQuery = this._buildDocsFTS(query);\n if (!ftsQuery) return [];\n\n try {\n const collectionFilter = collection ? 'AND d.collection = ?' : '';\n const params: any[] = [ftsQuery];\n if (collection) params.push(collection);\n params.push(k * 2);\n\n const rows = this._d.db.prepare(`\n SELECT d.*, bm25(fts_docs, 10.0, 2.0, 5.0, 1.0) AS bm25_score\n FROM fts_docs f\n JOIN doc_chunks d ON d.id = f.rowid\n WHERE fts_docs MATCH ? ${collectionFilter}\n ORDER BY bm25_score ASC\n LIMIT ?\n `).all(...params) as any[];\n\n return rows\n .map(r => ({\n type: 'document' as const,\n score: normalizeBM25(r.bm25_score),\n filePath: r.file_path,\n content: r.content,\n context: this._getDocContext(r.collection, r.file_path),\n metadata: {\n collection: r.collection,\n title: r.title,\n seq: r.seq,\n chunkId: r.id,\n },\n }))\n .filter(r => r.score >= minScore)\n .slice(0, k);\n } catch {\n return [];\n }\n }\n\n /** Build OR-mode FTS5 query for natural language doc search. */\n private _buildDocsFTS(query: string): string {\n const STOP_WORDS = new Set([\n 'the', 'is', 'at', 'which', 'on', 'a', 'an', 'and', 'or', 'but',\n 'in', 'with', 'to', 'for', 'of', 'by', 'from', 'as', 'it', 'its',\n 'this', 'that', 'be', 'are', 'was', 'were', 'been', 'has', 'have',\n 'had', 'do', 'does', 'did', 'can', 'could', 'will', 'would', 'how',\n 'what', 'when', 'where', 'who', 'why', 'not', 'no', 'so', 'if',\n ]);\n\n const clean = query\n .replace(/[{}[\\]()^~*:\"]/g, ' ')\n .replace(/\\bAND\\b|\\bOR\\b|\\bNOT\\b|\\bNEAR\\b/gi, '')\n .replace(/[_\\-./\\\\]/g, ' ')\n .trim();\n\n const words = clean.split(/\\s+/)\n .filter(w => w.length >= 3 && !STOP_WORDS.has(w.toLowerCase()));\n\n if (words.length === 0) return '';\n return words.map(w => `\"${w}\"`).join(' OR ');\n }\n\n /** Resolve context for a document (checks path_contexts tree → collection context). */\n private _getDocContext(collection: string, filePath: string): string | undefined {\n const parts = filePath.split('/');\n for (let i = parts.length; i >= 0; i--) {\n const checkPath = i === 0 ? '/' : '/' + parts.slice(0, i).join('/');\n const ctx = this._d.db.prepare(\n 'SELECT context FROM path_contexts WHERE collection = ? AND path = ?'\n ).get(collection, checkPath) as any;\n if (ctx) return ctx.context;\n }\n\n const coll = this._d.db.prepare(\n 'SELECT context FROM collections WHERE name = ?'\n ).get(collection) as any;\n return coll?.context ?? undefined;\n }\n}\n","/**\n * BrainBank — Docs Module\n * \n * Index any folder of markdown/text files (notes, docs, wikis).\n * Heading-aware smart chunking inspired by qmd.\n * \n * import { docs } from 'brainbank/docs';\n * brain.use(docs());\n */\n\nimport type { Plugin, PluginContext } from '@/indexers/base.ts';\nimport type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';\nimport type { Database } from '@/db/database.ts';\nimport type { EmbeddingProvider, DocumentCollection, SearchResult } from '@/types.ts';\nimport { DocsIndexer } from './docs-indexer.ts';\nimport { DocumentSearch } from './document-search.ts';\n\nclass DocsPlugin implements Plugin {\n readonly name = 'docs';\n hnsw!: HNSWIndex;\n indexer!: DocsIndexer;\n vecCache = new Map<number, Float32Array>();\n private _db!: Database;\n private _search!: DocumentSearch;\n\n constructor(private opts: { embeddingProvider?: EmbeddingProvider } = {}) {}\n\n async initialize(ctx: PluginContext): Promise<void> {\n this._db = ctx.db;\n const embedding = this.opts.embeddingProvider ?? ctx.embedding;\n\n this.hnsw = await ctx.createHnsw(undefined, embedding.dims);\n ctx.loadVectors('doc_vectors', 'chunk_id', this.hnsw, this.vecCache);\n this.indexer = new DocsIndexer(ctx.db, embedding, this.hnsw, this.vecCache);\n this._search = new DocumentSearch({\n db: ctx.db,\n embedding,\n hnsw: this.hnsw,\n vecCache: this.vecCache,\n reranker: ctx.config.reranker,\n });\n }\n\n /** Register a document collection. */\n addCollection(collection: DocumentCollection): void {\n this._db.prepare(`\n INSERT OR REPLACE INTO collections (name, path, pattern, ignore_json, context)\n VALUES (?, ?, ?, ?, ?)\n `).run(\n collection.name,\n collection.path,\n collection.pattern ?? '**/*.md',\n JSON.stringify(collection.ignore ?? []),\n collection.context ?? null,\n );\n }\n\n /** Remove a collection and its indexed data. */\n removeCollection(name: string): void {\n this.indexer.removeCollection(name);\n }\n\n /** List all registered collections. */\n listCollections(): DocumentCollection[] {\n return (this._db.prepare('SELECT * FROM collections').all() as any[]).map(row => ({\n name: row.name,\n path: row.path,\n pattern: row.pattern,\n ignore: JSON.parse(row.ignore_json),\n context: row.context,\n }));\n }\n\n /** Index all (or specific) collections. Incremental. */\n async indexCollections(options: {\n collections?: string[];\n onProgress?: (collection: string, file: string, current: number, total: number) => void;\n } = {}): Promise<Record<string, { indexed: number; skipped: number; chunks: number }>> {\n const allCollections = this.listCollections();\n const toIndex = options.collections\n ? allCollections.filter(c => options.collections!.includes(c.name))\n : allCollections;\n\n const results: Record<string, { indexed: number; skipped: number; chunks: number }> = {};\n\n for (const coll of toIndex) {\n results[coll.name] = await this.indexer.indexCollection(\n coll.name,\n coll.path,\n coll.pattern,\n {\n ignore: coll.ignore,\n onProgress: (file, cur, total) => options.onProgress?.(coll.name, file, cur, total),\n },\n );\n }\n\n return results;\n }\n\n /** Search documents using hybrid search (vector + BM25 → RRF). */\n async search(query: string, options?: {\n collection?: string;\n k?: number;\n minScore?: number;\n mode?: 'hybrid' | 'vector' | 'keyword';\n }): Promise<SearchResult[]> {\n return this._search.search(query, options);\n }\n\n /** Add context description for a document path. */\n addContext(collection: string, path: string, context: string): void {\n this._db.prepare(`\n INSERT OR REPLACE INTO path_contexts (collection, path, context)\n VALUES (?, ?, ?)\n `).run(collection, path, context);\n }\n\n /** Remove context for a path. */\n removeContext(collection: string, path: string): void {\n this._db.prepare(\n 'DELETE FROM path_contexts WHERE collection = ? AND path = ?'\n ).run(collection, path);\n }\n\n /** List all context entries. */\n listContexts(): { collection: string; path: string; context: string }[] {\n return this._db.prepare('SELECT * FROM path_contexts').all() as any[];\n }\n\n stats(): Record<string, any> {\n return {\n collections: (this._db.prepare('SELECT COUNT(*) as c FROM collections').get() as any).c,\n documents: (this._db.prepare('SELECT COUNT(DISTINCT file_path) as c FROM doc_chunks').get() as any).c,\n chunks: (this._db.prepare('SELECT COUNT(*) as c FROM doc_chunks').get() as any).c,\n hnswSize: this.hnsw.size,\n };\n }\n}\n\nexport interface DocsPluginOptions {\n /** Per-plugin embedding provider. Default: global embedding from BrainBank config. */\n embeddingProvider?: EmbeddingProvider;\n}\n\n/** Create a document collections plugin. */\nexport function docs(opts?: DocsPluginOptions): Plugin {\n return new DocsPlugin(opts);\n}\n"],"mappings":"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","names":["path"]}
|
package/dist/chunk-VVXYZIIB.js
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cosineSimilarity,
|
|
3
|
-
vecToBuffer
|
|
4
|
-
} from "./chunk-U2Q2XGPZ.js";
|
|
5
|
-
import {
|
|
6
|
-
__name
|
|
7
|
-
} from "./chunk-7QVYU63E.js";
|
|
8
|
-
|
|
9
|
-
// src/indexers/memory/pattern-store.ts
|
|
10
|
-
var PatternStore = class {
|
|
11
|
-
static {
|
|
12
|
-
__name(this, "PatternStore");
|
|
13
|
-
}
|
|
14
|
-
_deps;
|
|
15
|
-
constructor(deps) {
|
|
16
|
-
this._deps = deps;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Store a learned pattern.
|
|
20
|
-
* Returns the pattern ID.
|
|
21
|
-
*/
|
|
22
|
-
async learn(pattern) {
|
|
23
|
-
const result = this._deps.db.prepare(`
|
|
24
|
-
INSERT INTO memory_patterns (task_type, task, approach, outcome, success_rate, critique, tokens_used, latency_ms)
|
|
25
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
26
|
-
`).run(
|
|
27
|
-
pattern.taskType,
|
|
28
|
-
pattern.task,
|
|
29
|
-
pattern.approach,
|
|
30
|
-
pattern.outcome ?? null,
|
|
31
|
-
pattern.successRate,
|
|
32
|
-
pattern.critique ?? null,
|
|
33
|
-
pattern.tokensUsed ?? null,
|
|
34
|
-
pattern.latencyMs ?? null
|
|
35
|
-
);
|
|
36
|
-
const id = Number(result.lastInsertRowid);
|
|
37
|
-
const text = `${pattern.taskType} ${pattern.task} ${pattern.approach}`;
|
|
38
|
-
const vec = await this._deps.embedding.embed(text);
|
|
39
|
-
this._deps.db.prepare(
|
|
40
|
-
"INSERT INTO memory_vectors (pattern_id, embedding) VALUES (?, ?)"
|
|
41
|
-
).run(id, vecToBuffer(vec));
|
|
42
|
-
this._deps.hnsw.add(vec, id);
|
|
43
|
-
this._deps.vectorCache.set(id, vec);
|
|
44
|
-
return id;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Search for similar successful patterns.
|
|
48
|
-
* Filters by minimum success rate.
|
|
49
|
-
*/
|
|
50
|
-
async search(query, k = 4, minSuccess = 0.5) {
|
|
51
|
-
if (this._deps.hnsw.size === 0) return [];
|
|
52
|
-
const vec = await this._deps.embedding.embed(query);
|
|
53
|
-
const hits = this._deps.hnsw.search(vec, k * 2);
|
|
54
|
-
if (hits.length === 0) return [];
|
|
55
|
-
const ids = hits.map((h) => h.id);
|
|
56
|
-
const scoreMap = new Map(hits.map((h) => [h.id, h.score]));
|
|
57
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
58
|
-
const rows = this._deps.db.prepare(
|
|
59
|
-
`SELECT * FROM memory_patterns WHERE id IN (${placeholders}) AND success_rate >= ?`
|
|
60
|
-
).all(...ids, minSuccess);
|
|
61
|
-
return rows.map((r) => ({
|
|
62
|
-
id: r.id,
|
|
63
|
-
taskType: r.task_type,
|
|
64
|
-
task: r.task,
|
|
65
|
-
approach: r.approach,
|
|
66
|
-
outcome: r.outcome,
|
|
67
|
-
successRate: r.success_rate,
|
|
68
|
-
critique: r.critique,
|
|
69
|
-
tokensUsed: r.tokens_used,
|
|
70
|
-
latencyMs: r.latency_ms,
|
|
71
|
-
score: scoreMap.get(r.id) ?? 0
|
|
72
|
-
})).sort((a, b) => b.score - a.score).slice(0, k);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Get all patterns for a specific task type.
|
|
76
|
-
*/
|
|
77
|
-
getByTaskType(taskType, limit = 20) {
|
|
78
|
-
const rows = this._deps.db.prepare(
|
|
79
|
-
`SELECT * FROM memory_patterns WHERE task_type = ? ORDER BY success_rate DESC LIMIT ?`
|
|
80
|
-
).all(taskType, limit);
|
|
81
|
-
return rows.map((r) => ({
|
|
82
|
-
id: r.id,
|
|
83
|
-
taskType: r.task_type,
|
|
84
|
-
task: r.task,
|
|
85
|
-
approach: r.approach,
|
|
86
|
-
outcome: r.outcome,
|
|
87
|
-
successRate: r.success_rate,
|
|
88
|
-
critique: r.critique,
|
|
89
|
-
tokensUsed: r.tokens_used,
|
|
90
|
-
latencyMs: r.latency_ms
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
/** Total number of stored patterns. */
|
|
94
|
-
get count() {
|
|
95
|
-
return this._deps.db.prepare("SELECT COUNT(*) as c FROM memory_patterns").get().c;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// src/indexers/memory/consolidator.ts
|
|
100
|
-
var Consolidator = class {
|
|
101
|
-
constructor(_db, _vectorCache) {
|
|
102
|
-
this._db = _db;
|
|
103
|
-
this._vectorCache = _vectorCache;
|
|
104
|
-
}
|
|
105
|
-
static {
|
|
106
|
-
__name(this, "Consolidator");
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Remove old failed patterns.
|
|
110
|
-
* Criteria: success_rate < 0.3 AND created > 90 days ago.
|
|
111
|
-
*/
|
|
112
|
-
prune(maxAgeDays = 90, minSuccess = 0.3) {
|
|
113
|
-
const cutoff = Math.floor(Date.now() / 1e3) - maxAgeDays * 86400;
|
|
114
|
-
const result = this._db.prepare(
|
|
115
|
-
"DELETE FROM memory_patterns WHERE success_rate < ? AND created_at < ?"
|
|
116
|
-
).run(minSuccess, cutoff);
|
|
117
|
-
return result.changes;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Merge near-duplicate patterns.
|
|
121
|
-
* Keeps the one with higher success_rate.
|
|
122
|
-
* Threshold: cosine similarity > 0.95.
|
|
123
|
-
*/
|
|
124
|
-
dedup(threshold = 0.95) {
|
|
125
|
-
const entries = Array.from(this._vectorCache.entries());
|
|
126
|
-
const toDelete = /* @__PURE__ */ new Set();
|
|
127
|
-
for (let i = 0; i < entries.length; i++) {
|
|
128
|
-
if (toDelete.has(entries[i][0])) continue;
|
|
129
|
-
for (let j = i + 1; j < entries.length; j++) {
|
|
130
|
-
if (toDelete.has(entries[j][0])) continue;
|
|
131
|
-
const sim = cosineSimilarity(entries[i][1], entries[j][1]);
|
|
132
|
-
if (sim > threshold) {
|
|
133
|
-
const pi = this._db.prepare(
|
|
134
|
-
"SELECT success_rate FROM memory_patterns WHERE id = ?"
|
|
135
|
-
).get(entries[i][0]);
|
|
136
|
-
const pj = this._db.prepare(
|
|
137
|
-
"SELECT success_rate FROM memory_patterns WHERE id = ?"
|
|
138
|
-
).get(entries[j][0]);
|
|
139
|
-
if (pi && pj) {
|
|
140
|
-
const deleteId = pi.success_rate >= pj.success_rate ? entries[j][0] : entries[i][0];
|
|
141
|
-
toDelete.add(deleteId);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (toDelete.size > 0) {
|
|
147
|
-
const ids = Array.from(toDelete);
|
|
148
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
149
|
-
this._db.prepare(
|
|
150
|
-
`DELETE FROM memory_patterns WHERE id IN (${placeholders})`
|
|
151
|
-
).run(...ids);
|
|
152
|
-
for (const id of ids) {
|
|
153
|
-
this._vectorCache.delete(id);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return toDelete.size;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Run full consolidation: prune + dedup.
|
|
160
|
-
*/
|
|
161
|
-
consolidate() {
|
|
162
|
-
const pruned = this.prune();
|
|
163
|
-
const deduped = this.dedup();
|
|
164
|
-
return { pruned, deduped };
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// src/indexers/memory/pattern-distiller.ts
|
|
169
|
-
var PatternDistiller = class {
|
|
170
|
-
constructor(_db) {
|
|
171
|
-
this._db = _db;
|
|
172
|
-
}
|
|
173
|
-
static {
|
|
174
|
-
__name(this, "PatternDistiller");
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Distill top patterns for a task type into a strategy.
|
|
178
|
-
* Updates the distilled_strategies table.
|
|
179
|
-
*/
|
|
180
|
-
distill(taskType, topK = 10) {
|
|
181
|
-
const patterns = this._db.prepare(`
|
|
182
|
-
SELECT task, approach, outcome, critique, success_rate
|
|
183
|
-
FROM memory_patterns
|
|
184
|
-
WHERE task_type = ? AND success_rate >= 0.7
|
|
185
|
-
ORDER BY success_rate DESC, created_at DESC
|
|
186
|
-
LIMIT ?
|
|
187
|
-
`).all(taskType, topK);
|
|
188
|
-
if (patterns.length === 0) return null;
|
|
189
|
-
const lines = [];
|
|
190
|
-
const avgSuccess = patterns.reduce((sum, p) => sum + p.success_rate, 0) / patterns.length;
|
|
191
|
-
lines.push(`Strategy for "${taskType}" (${patterns.length} patterns, avg success ${Math.round(avgSuccess * 100)}%):`);
|
|
192
|
-
lines.push("");
|
|
193
|
-
for (const p of patterns) {
|
|
194
|
-
lines.push(`\u2022 ${p.approach} (${Math.round(p.success_rate * 100)}%)`);
|
|
195
|
-
if (p.critique) lines.push(` \u2514 ${p.critique}`);
|
|
196
|
-
}
|
|
197
|
-
const strategy = lines.join("\n");
|
|
198
|
-
const confidence = avgSuccess;
|
|
199
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
200
|
-
this._db.prepare(`
|
|
201
|
-
INSERT INTO distilled_strategies (task_type, strategy, confidence, updated_at)
|
|
202
|
-
VALUES (?, ?, ?, ?)
|
|
203
|
-
ON CONFLICT(task_type) DO UPDATE SET
|
|
204
|
-
strategy = excluded.strategy,
|
|
205
|
-
confidence = excluded.confidence,
|
|
206
|
-
updated_at = excluded.updated_at
|
|
207
|
-
`).run(taskType, strategy, confidence, now);
|
|
208
|
-
return { taskType, strategy, confidence, updatedAt: now };
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Get a distilled strategy for a task type.
|
|
212
|
-
*/
|
|
213
|
-
get(taskType) {
|
|
214
|
-
const row = this._db.prepare(
|
|
215
|
-
"SELECT * FROM distilled_strategies WHERE task_type = ?"
|
|
216
|
-
).get(taskType);
|
|
217
|
-
if (!row) return null;
|
|
218
|
-
return {
|
|
219
|
-
taskType: row.task_type,
|
|
220
|
-
strategy: row.strategy,
|
|
221
|
-
confidence: row.confidence,
|
|
222
|
-
updatedAt: row.updated_at
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* List all distilled strategies.
|
|
227
|
-
*/
|
|
228
|
-
list() {
|
|
229
|
-
const rows = this._db.prepare(
|
|
230
|
-
"SELECT * FROM distilled_strategies ORDER BY confidence DESC"
|
|
231
|
-
).all();
|
|
232
|
-
return rows.map((r) => ({
|
|
233
|
-
taskType: r.task_type,
|
|
234
|
-
strategy: r.strategy,
|
|
235
|
-
confidence: r.confidence,
|
|
236
|
-
updatedAt: r.updated_at
|
|
237
|
-
}));
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// src/indexers/memory/memory-plugin.ts
|
|
242
|
-
var MemoryPlugin = class {
|
|
243
|
-
static {
|
|
244
|
-
__name(this, "MemoryPlugin");
|
|
245
|
-
}
|
|
246
|
-
name = "memory";
|
|
247
|
-
hnsw;
|
|
248
|
-
patternStore;
|
|
249
|
-
consolidator;
|
|
250
|
-
distiller;
|
|
251
|
-
vecCache = /* @__PURE__ */ new Map();
|
|
252
|
-
_db;
|
|
253
|
-
async initialize(ctx) {
|
|
254
|
-
this._db = ctx.db;
|
|
255
|
-
this.hnsw = await ctx.createHnsw(1e5);
|
|
256
|
-
ctx.loadVectors("memory_vectors", "pattern_id", this.hnsw, this.vecCache);
|
|
257
|
-
this.patternStore = new PatternStore({
|
|
258
|
-
db: ctx.db,
|
|
259
|
-
hnsw: this.hnsw,
|
|
260
|
-
vectorCache: this.vecCache,
|
|
261
|
-
embedding: ctx.embedding
|
|
262
|
-
});
|
|
263
|
-
this.consolidator = new Consolidator(ctx.db, this.vecCache);
|
|
264
|
-
this.distiller = new PatternDistiller(ctx.db);
|
|
265
|
-
}
|
|
266
|
-
/** Store a learned pattern. */
|
|
267
|
-
async learn(pattern) {
|
|
268
|
-
const id = await this.patternStore.learn(pattern);
|
|
269
|
-
if (this.patternStore.count > 0 && this.patternStore.count % 50 === 0) {
|
|
270
|
-
this.consolidator.consolidate();
|
|
271
|
-
}
|
|
272
|
-
return id;
|
|
273
|
-
}
|
|
274
|
-
/** Search for similar patterns. */
|
|
275
|
-
async search(query, k = 4) {
|
|
276
|
-
return this.patternStore.search(query, k);
|
|
277
|
-
}
|
|
278
|
-
/** Consolidate: prune old failures + deduplicate. */
|
|
279
|
-
consolidate() {
|
|
280
|
-
return this.consolidator.consolidate();
|
|
281
|
-
}
|
|
282
|
-
/** Distill patterns into a strategy. */
|
|
283
|
-
distill(taskType) {
|
|
284
|
-
return this.distiller.distill(taskType);
|
|
285
|
-
}
|
|
286
|
-
stats() {
|
|
287
|
-
return {
|
|
288
|
-
patterns: this.patternStore.count,
|
|
289
|
-
avgSuccess: this._db.prepare("SELECT AVG(success_rate) as a FROM memory_patterns").get().a ?? 0,
|
|
290
|
-
hnswSize: this.hnsw.size
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
function memory() {
|
|
295
|
-
return new MemoryPlugin();
|
|
296
|
-
}
|
|
297
|
-
__name(memory, "memory");
|
|
298
|
-
|
|
299
|
-
export {
|
|
300
|
-
PatternStore,
|
|
301
|
-
Consolidator,
|
|
302
|
-
memory
|
|
303
|
-
};
|
|
304
|
-
//# sourceMappingURL=chunk-VVXYZIIB.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/indexers/memory/pattern-store.ts","../src/indexers/memory/consolidator.ts","../src/indexers/memory/pattern-distiller.ts","../src/indexers/memory/memory-plugin.ts"],"sourcesContent":["/**\n * BrainBank — Pattern Store (Agent Memory)\n * \n * Stores what the agent learned from past tasks.\n * Each pattern records task, approach, and success rate.\n * Searchable by semantic similarity via HNSW.\n */\n\nimport type { Database } from '@/db/database.ts';\nimport { vecToBuffer } from '@/lib/math.ts';\nimport type { EmbeddingProvider, LearningPattern } from '@/types.ts';\nimport type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';\n\nexport interface PatternStoreDeps {\n db: Database;\n hnsw: HNSWIndex;\n vectorCache: Map<number, Float32Array>;\n embedding: EmbeddingProvider;\n}\n\nexport class PatternStore {\n private _deps: PatternStoreDeps;\n\n constructor(deps: PatternStoreDeps) {\n this._deps = deps;\n }\n\n /**\n * Store a learned pattern.\n * Returns the pattern ID.\n */\n async learn(pattern: LearningPattern): Promise<number> {\n const result = this._deps.db.prepare(`\n INSERT INTO memory_patterns (task_type, task, approach, outcome, success_rate, critique, tokens_used, latency_ms)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n pattern.taskType,\n pattern.task,\n pattern.approach,\n pattern.outcome ?? null,\n pattern.successRate,\n pattern.critique ?? null,\n pattern.tokensUsed ?? null,\n pattern.latencyMs ?? null,\n );\n\n const id = Number(result.lastInsertRowid);\n\n // Embed and store vector\n const text = `${pattern.taskType} ${pattern.task} ${pattern.approach}`;\n const vec = await this._deps.embedding.embed(text);\n\n this._deps.db.prepare(\n 'INSERT INTO memory_vectors (pattern_id, embedding) VALUES (?, ?)'\n ).run(id, vecToBuffer(vec));\n\n this._deps.hnsw.add(vec, id);\n this._deps.vectorCache.set(id, vec);\n\n return id;\n }\n\n /**\n * Search for similar successful patterns.\n * Filters by minimum success rate.\n */\n async search(query: string, k: number = 4, minSuccess: number = 0.5): Promise<(LearningPattern & { score: number })[]> {\n if (this._deps.hnsw.size === 0) return [];\n\n const vec = await this._deps.embedding.embed(query);\n const hits = this._deps.hnsw.search(vec, k * 2);\n\n if (hits.length === 0) return [];\n\n const ids = hits.map(h => h.id);\n const scoreMap = new Map(hits.map(h => [h.id, h.score]));\n\n const placeholders = ids.map(() => '?').join(',');\n const rows = this._deps.db.prepare(\n `SELECT * FROM memory_patterns WHERE id IN (${placeholders}) AND success_rate >= ?`\n ).all(...ids, minSuccess) as any[];\n\n return rows\n .map(r => ({\n id: r.id,\n taskType: r.task_type,\n task: r.task,\n approach: r.approach,\n outcome: r.outcome,\n successRate: r.success_rate,\n critique: r.critique,\n tokensUsed: r.tokens_used,\n latencyMs: r.latency_ms,\n score: scoreMap.get(r.id) ?? 0,\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n\n /**\n * Get all patterns for a specific task type.\n */\n getByTaskType(taskType: string, limit: number = 20): LearningPattern[] {\n const rows = this._deps.db.prepare(\n `SELECT * FROM memory_patterns WHERE task_type = ? ORDER BY success_rate DESC LIMIT ?`\n ).all(taskType, limit) as any[];\n\n return rows.map(r => ({\n id: r.id,\n taskType: r.task_type,\n task: r.task,\n approach: r.approach,\n outcome: r.outcome,\n successRate: r.success_rate,\n critique: r.critique,\n tokensUsed: r.tokens_used,\n latencyMs: r.latency_ms,\n }));\n }\n\n /** Total number of stored patterns. */\n get count(): number {\n return (this._deps.db.prepare('SELECT COUNT(*) as c FROM memory_patterns').get() as any).c;\n }\n}\n","/**\n * BrainBank — Consolidator\n * \n * Maintenance operations for the agent memory:\n * - prune: remove old failed patterns\n * - dedup: merge near-duplicate patterns (cosine > 0.95)\n * - consolidate: run both\n */\n\nimport type { Database } from '@/db/database.ts';\nimport { cosineSimilarity } from '@/lib/math.ts';\n\nexport class Consolidator {\n constructor(\n private _db: Database,\n private _vectorCache: Map<number, Float32Array>,\n ) {}\n\n /**\n * Remove old failed patterns.\n * Criteria: success_rate < 0.3 AND created > 90 days ago.\n */\n prune(maxAgeDays: number = 90, minSuccess: number = 0.3): number {\n const cutoff = Math.floor(Date.now() / 1000) - maxAgeDays * 86400;\n const result = this._db.prepare(\n 'DELETE FROM memory_patterns WHERE success_rate < ? AND created_at < ?'\n ).run(minSuccess, cutoff);\n return result.changes;\n }\n\n /**\n * Merge near-duplicate patterns.\n * Keeps the one with higher success_rate.\n * Threshold: cosine similarity > 0.95.\n */\n dedup(threshold: number = 0.95): number {\n const entries = Array.from(this._vectorCache.entries());\n const toDelete = new Set<number>();\n\n for (let i = 0; i < entries.length; i++) {\n if (toDelete.has(entries[i][0])) continue;\n\n for (let j = i + 1; j < entries.length; j++) {\n if (toDelete.has(entries[j][0])) continue;\n\n const sim = cosineSimilarity(entries[i][1], entries[j][1]);\n if (sim > threshold) {\n // Keep the one with higher success rate\n const pi = this._db.prepare(\n 'SELECT success_rate FROM memory_patterns WHERE id = ?'\n ).get(entries[i][0]) as any;\n const pj = this._db.prepare(\n 'SELECT success_rate FROM memory_patterns WHERE id = ?'\n ).get(entries[j][0]) as any;\n\n if (pi && pj) {\n const deleteId = pi.success_rate >= pj.success_rate\n ? entries[j][0]\n : entries[i][0];\n toDelete.add(deleteId);\n }\n }\n }\n }\n\n if (toDelete.size > 0) {\n const ids = Array.from(toDelete);\n const placeholders = ids.map(() => '?').join(',');\n this._db.prepare(\n `DELETE FROM memory_patterns WHERE id IN (${placeholders})`\n ).run(...ids);\n\n // Clean vector cache\n for (const id of ids) {\n this._vectorCache.delete(id);\n }\n }\n\n return toDelete.size;\n }\n\n /**\n * Run full consolidation: prune + dedup.\n */\n consolidate(): { pruned: number; deduped: number } {\n const pruned = this.prune();\n const deduped = this.dedup();\n return { pruned, deduped };\n }\n}\n","/**\n * BrainBank — Strategy Distiller\n * \n * Aggregates top patterns for a task type into a single strategy text.\n * Analogous to SONA's Deep Loop — periodic knowledge distillation.\n */\n\nimport type { Database } from '@/db/database.ts';\nimport type { DistilledStrategy } from '@/types.ts';\n\nexport class PatternDistiller {\n constructor(private _db: Database) {}\n\n /**\n * Distill top patterns for a task type into a strategy.\n * Updates the distilled_strategies table.\n */\n distill(taskType: string, topK: number = 10): DistilledStrategy | null {\n const patterns = this._db.prepare(`\n SELECT task, approach, outcome, critique, success_rate\n FROM memory_patterns\n WHERE task_type = ? AND success_rate >= 0.7\n ORDER BY success_rate DESC, created_at DESC\n LIMIT ?\n `).all(taskType, topK) as any[];\n\n if (patterns.length === 0) return null;\n\n // Build strategy text from top patterns\n const lines: string[] = [];\n const avgSuccess = patterns.reduce((sum: number, p: any) => sum + p.success_rate, 0) / patterns.length;\n\n lines.push(`Strategy for \"${taskType}\" (${patterns.length} patterns, avg success ${Math.round(avgSuccess * 100)}%):`);\n lines.push('');\n\n for (const p of patterns) {\n lines.push(`• ${p.approach} (${Math.round(p.success_rate * 100)}%)`);\n if (p.critique) lines.push(` └ ${p.critique}`);\n }\n\n const strategy = lines.join('\\n');\n const confidence = avgSuccess;\n const now = Math.floor(Date.now() / 1000);\n\n this._db.prepare(`\n INSERT INTO distilled_strategies (task_type, strategy, confidence, updated_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(task_type) DO UPDATE SET\n strategy = excluded.strategy,\n confidence = excluded.confidence,\n updated_at = excluded.updated_at\n `).run(taskType, strategy, confidence, now);\n\n return { taskType, strategy, confidence, updatedAt: now };\n }\n\n /**\n * Get a distilled strategy for a task type.\n */\n get(taskType: string): DistilledStrategy | null {\n const row = this._db.prepare(\n 'SELECT * FROM distilled_strategies WHERE task_type = ?'\n ).get(taskType) as any;\n\n if (!row) return null;\n return {\n taskType: row.task_type,\n strategy: row.strategy,\n confidence: row.confidence,\n updatedAt: row.updated_at,\n };\n }\n\n /**\n * List all distilled strategies.\n */\n list(): DistilledStrategy[] {\n const rows = this._db.prepare(\n 'SELECT * FROM distilled_strategies ORDER BY confidence DESC'\n ).all() as any[];\n\n return rows.map(r => ({\n taskType: r.task_type,\n strategy: r.strategy,\n confidence: r.confidence,\n updatedAt: r.updated_at,\n }));\n }\n}\n","/**\n * BrainBank — Memory Plugin\n * \n * Agent learns from completed tasks — stores patterns,\n * consolidates failures, distills strategies.\n * \n * import { memory } from 'brainbank/memory';\n * brain.use(memory());\n */\n\nimport type { Plugin, PluginContext } from '@/indexers/base.ts';\nimport type { HNSWIndex } from '@/providers/vector/hnsw-index.ts';\nimport type { Database } from '@/db/database.ts';\nimport { PatternStore } from './pattern-store.ts';\nimport { Consolidator } from './consolidator.ts';\nimport { PatternDistiller } from './pattern-distiller.ts';\nimport type { LearningPattern, DistilledStrategy } from '@/types.ts';\n\nclass MemoryPlugin implements Plugin {\n readonly name = 'memory';\n hnsw!: HNSWIndex;\n patternStore!: PatternStore;\n consolidator!: Consolidator;\n distiller!: PatternDistiller;\n vecCache = new Map<number, Float32Array>();\n private _db!: Database;\n\n async initialize(ctx: PluginContext): Promise<void> {\n this._db = ctx.db;\n this.hnsw = await ctx.createHnsw(100_000);\n ctx.loadVectors('memory_vectors', 'pattern_id', this.hnsw, this.vecCache);\n\n this.patternStore = new PatternStore({\n db: ctx.db,\n hnsw: this.hnsw,\n vectorCache: this.vecCache,\n embedding: ctx.embedding,\n });\n\n this.consolidator = new Consolidator(ctx.db, this.vecCache);\n this.distiller = new PatternDistiller(ctx.db);\n }\n\n /** Store a learned pattern. */\n async learn(pattern: LearningPattern): Promise<number> {\n const id = await this.patternStore.learn(pattern);\n\n // Auto-consolidate every 50 patterns (guard against count=0)\n if (this.patternStore.count > 0 && this.patternStore.count % 50 === 0) {\n this.consolidator.consolidate();\n }\n\n return id;\n }\n\n /** Search for similar patterns. */\n async search(query: string, k: number = 4): Promise<(LearningPattern & { score: number })[]> {\n return this.patternStore.search(query, k);\n }\n\n /** Consolidate: prune old failures + deduplicate. */\n consolidate(): { pruned: number; deduped: number } {\n return this.consolidator.consolidate();\n }\n\n /** Distill patterns into a strategy. */\n distill(taskType: string): DistilledStrategy | null {\n return this.distiller.distill(taskType);\n }\n\n stats(): Record<string, any> {\n return {\n patterns: this.patternStore.count,\n avgSuccess: (this._db.prepare('SELECT AVG(success_rate) as a FROM memory_patterns').get() as any).a ?? 0,\n hnswSize: this.hnsw.size,\n };\n }\n}\n\n/** Create an agent memory plugin. */\nexport function memory(): Plugin {\n return new MemoryPlugin();\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-YOLKSYWK.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__name
|
|
3
|
-
} from "./chunk-7QVYU63E.js";
|
|
4
|
-
|
|
5
|
-
// src/lib/rrf.ts
|
|
6
|
-
function reciprocalRankFusion(resultSets, k = 60, maxResults = 15) {
|
|
7
|
-
const fused = /* @__PURE__ */ new Map();
|
|
8
|
-
for (const results of resultSets) {
|
|
9
|
-
for (let rank = 0; rank < results.length; rank++) {
|
|
10
|
-
const r = results[rank];
|
|
11
|
-
const key = resultKey(r);
|
|
12
|
-
const rrfContribution = 1 / (k + rank + 1);
|
|
13
|
-
const existing = fused.get(key);
|
|
14
|
-
if (existing) {
|
|
15
|
-
existing.rrfScore += rrfContribution;
|
|
16
|
-
if (r.score > existing.result.score) {
|
|
17
|
-
existing.result = { ...r };
|
|
18
|
-
}
|
|
19
|
-
} else {
|
|
20
|
-
fused.set(key, {
|
|
21
|
-
result: { ...r },
|
|
22
|
-
rrfScore: rrfContribution
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const sorted = Array.from(fused.values()).sort((a, b) => b.rrfScore - a.rrfScore).slice(0, maxResults);
|
|
28
|
-
const maxRRF = sorted[0]?.rrfScore ?? 1;
|
|
29
|
-
return sorted.map((entry) => ({
|
|
30
|
-
...entry.result,
|
|
31
|
-
score: entry.rrfScore / maxRRF,
|
|
32
|
-
metadata: {
|
|
33
|
-
...entry.result.metadata,
|
|
34
|
-
rrfScore: entry.rrfScore
|
|
35
|
-
}
|
|
36
|
-
}));
|
|
37
|
-
}
|
|
38
|
-
__name(reciprocalRankFusion, "reciprocalRankFusion");
|
|
39
|
-
function resultKey(r) {
|
|
40
|
-
switch (r.type) {
|
|
41
|
-
case "code":
|
|
42
|
-
return `code:${r.filePath}:${r.metadata.startLine}-${r.metadata.endLine}`;
|
|
43
|
-
case "commit":
|
|
44
|
-
return `commit:${r.metadata.hash || r.metadata.shortHash}`;
|
|
45
|
-
case "pattern":
|
|
46
|
-
return `pattern:${r.metadata.taskType}:${r.content?.slice(0, 60)}`;
|
|
47
|
-
case "document":
|
|
48
|
-
return `document:${r.filePath ?? ""}:${r.metadata.collection ?? ""}:${r.metadata.seq ?? ""}:${r.content?.slice(0, 80)}`;
|
|
49
|
-
case "collection":
|
|
50
|
-
return `collection:${r.metadata.id ?? r.content?.slice(0, 80)}`;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
__name(resultKey, "resultKey");
|
|
54
|
-
|
|
55
|
-
// src/lib/fts.ts
|
|
56
|
-
function splitCompound(word) {
|
|
57
|
-
return word.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./\\]/g, " ").trim();
|
|
58
|
-
}
|
|
59
|
-
__name(splitCompound, "splitCompound");
|
|
60
|
-
function sanitizeFTS(query) {
|
|
61
|
-
const clean = query.replace(/[{}[\]()^~*:]/g, " ").replace(/\bAND\b|\bOR\b|\bNOT\b|\bNEAR\b/gi, "").trim();
|
|
62
|
-
const expanded = clean.split(/\s+/).map((w) => splitCompound(w)).join(" ");
|
|
63
|
-
const words = expanded.split(/\s+/).filter((w) => w.length > 1);
|
|
64
|
-
if (words.length === 0) return "";
|
|
65
|
-
return words.map((w) => `"${w}"`).join(" ");
|
|
66
|
-
}
|
|
67
|
-
__name(sanitizeFTS, "sanitizeFTS");
|
|
68
|
-
function normalizeBM25(rawScore) {
|
|
69
|
-
const abs = Math.abs(rawScore);
|
|
70
|
-
return 1 / (1 + Math.exp(-0.3 * (abs - 5)));
|
|
71
|
-
}
|
|
72
|
-
__name(normalizeBM25, "normalizeBM25");
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
reciprocalRankFusion,
|
|
76
|
-
sanitizeFTS,
|
|
77
|
-
normalizeBM25
|
|
78
|
-
};
|
|
79
|
-
//# sourceMappingURL=chunk-YOLKSYWK.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/rrf.ts","../src/lib/fts.ts"],"sourcesContent":["/**\n * BrainBank — Reciprocal Rank Fusion (RRF)\n * \n * Combines results from multiple search systems (vector + BM25)\n * using the RRF algorithm: score = Σ 1/(k + rank_i)\n * \n * This is the same algorithm used by Elasticsearch, QMD, and most\n * production hybrid search systems. Simple but very effective.\n * \n * Reference: Cormack et al., \"Reciprocal Rank Fusion outperforms\n * Condorcet and individual Rank Learning Methods\" (2009)\n */\n\nimport type { SearchResult } from '@/types.ts';\n\n/**\n * Fuse ranked lists from different search systems into a single ranked list.\n * \n * @param resultSets - Arrays of SearchResult from different systems (e.g. vector, BM25)\n * @param k - Smoothing constant. Default: 60 (standard value). Higher = less emphasis on top ranks.\n * @param maxResults - Maximum results to return.\n */\nexport function reciprocalRankFusion(\n resultSets: SearchResult[][],\n k: number = 60,\n maxResults: number = 15,\n): SearchResult[] {\n // Build a map: unique key → { bestResult, rrfScore }\n const fused = new Map<string, { result: SearchResult; rrfScore: number }>();\n\n for (const results of resultSets) {\n for (let rank = 0; rank < results.length; rank++) {\n const r = results[rank];\n const key = resultKey(r);\n const rrfContribution = 1.0 / (k + rank + 1);\n\n const existing = fused.get(key);\n if (existing) {\n existing.rrfScore += rrfContribution;\n // Keep the result with the higher original score\n if (r.score > existing.result.score) {\n existing.result = { ...r };\n }\n } else {\n fused.set(key, {\n result: { ...r },\n rrfScore: rrfContribution,\n });\n }\n }\n }\n\n // Sort by RRF score descending, normalize, and return\n const sorted = Array.from(fused.values())\n .sort((a, b) => b.rrfScore - a.rrfScore)\n .slice(0, maxResults);\n\n // Normalize RRF scores to 0..1 range.\n // Note: A single result always normalizes to 1.0. This is correct for RRF —\n // the score is relative to the result set, not absolute relevance.\n // Use minScore filters sparingly with RRF.\n const maxRRF = sorted[0]?.rrfScore ?? 1;\n return sorted.map(entry => ({\n ...entry.result,\n score: entry.rrfScore / maxRRF,\n metadata: {\n ...entry.result.metadata,\n rrfScore: entry.rrfScore,\n } as any,\n }));\n}\n\n/**\n * Generate a unique key for a search result to detect duplicates across systems.\n */\nfunction resultKey(r: SearchResult): string {\n switch (r.type) {\n case 'code':\n return `code:${r.filePath}:${r.metadata.startLine}-${r.metadata.endLine}`;\n case 'commit':\n return `commit:${r.metadata.hash || r.metadata.shortHash}`;\n case 'pattern':\n return `pattern:${r.metadata.taskType}:${r.content?.slice(0, 60)}`;\n case 'document':\n return `document:${r.filePath ?? ''}:${(r.metadata as any).collection ?? ''}:${(r.metadata as any).seq ?? ''}:${r.content?.slice(0, 80)}`;\n case 'collection':\n return `collection:${(r.metadata as any).id ?? r.content?.slice(0, 80)}`;\n }\n}\n","/**\n * BrainBank — FTS Utilities\n * \n * Shared helpers for SQLite FTS5 query sanitization.\n */\n\n/**\n * Split camelCase, PascalCase, and snake_case into individual words.\n * \"MagicLinkCallback\" → \"Magic Link Callback\"\n * \"tenant_worker\" → \"tenant worker\"\n */\nfunction splitCompound(word: string): string {\n return word\n .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // HTMLParser → HTML Parser\n .replace(/[_\\-./\\\\]/g, ' ') // snake_case, kebab-case, paths\n .trim();\n}\n\n/**\n * Sanitize a user query for FTS5 syntax.\n * Strips operators that would cause parse errors, splits compound words,\n * and converts words to implicit AND with exact-match quoting.\n */\nexport function sanitizeFTS(query: string): string {\n const clean = query\n .replace(/[{}[\\]()^~*:]/g, ' ')\n .replace(/\\bAND\\b|\\bOR\\b|\\bNOT\\b|\\bNEAR\\b/gi, '')\n .trim();\n\n // Split compound words (camelCase, PascalCase, snake_case)\n const expanded = clean.split(/\\s+/)\n .map(w => splitCompound(w))\n .join(' ');\n\n const words = expanded.split(/\\s+/).filter(w => w.length > 1);\n if (words.length === 0) return '';\n\n return words.map(w => `\"${w}\"`).join(' ');\n}\n\n/**\n * Normalize BM25 score from SQLite (negative, lower = better)\n * to 0.0–1.0 (higher = better) for consistency with vector search.\n */\nexport function normalizeBM25(rawScore: number): number {\n const abs = Math.abs(rawScore);\n return 1.0 / (1.0 + Math.exp(-0.3 * (abs - 5)));\n}\n"],"mappings":";;;;;AAsBO,SAAS,qBACZ,YACA,IAAY,IACZ,aAAqB,IACP;AAEd,QAAM,QAAQ,oBAAI,IAAwD;AAE1E,aAAW,WAAW,YAAY;AAC9B,aAAS,OAAO,GAAG,OAAO,QAAQ,QAAQ,QAAQ;AAC9C,YAAM,IAAI,QAAQ,IAAI;AACtB,YAAM,MAAM,UAAU,CAAC;AACvB,YAAM,kBAAkB,KAAO,IAAI,OAAO;AAE1C,YAAM,WAAW,MAAM,IAAI,GAAG;AAC9B,UAAI,UAAU;AACV,iBAAS,YAAY;AAErB,YAAI,EAAE,QAAQ,SAAS,OAAO,OAAO;AACjC,mBAAS,SAAS,EAAE,GAAG,EAAE;AAAA,QAC7B;AAAA,MACJ,OAAO;AACH,cAAM,IAAI,KAAK;AAAA,UACX,QAAQ,EAAE,GAAG,EAAE;AAAA,UACf,UAAU;AAAA,QACd,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AAGA,QAAM,SAAS,MAAM,KAAK,MAAM,OAAO,CAAC,EACnC,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,MAAM,GAAG,UAAU;AAMxB,QAAM,SAAS,OAAO,CAAC,GAAG,YAAY;AACtC,SAAO,OAAO,IAAI,YAAU;AAAA,IACxB,GAAG,MAAM;AAAA,IACT,OAAO,MAAM,WAAW;AAAA,IACxB,UAAU;AAAA,MACN,GAAG,MAAM,OAAO;AAAA,MAChB,UAAU,MAAM;AAAA,IACpB;AAAA,EACJ,EAAE;AACN;AAhDgB;AAqDhB,SAAS,UAAU,GAAyB;AACxC,UAAQ,EAAE,MAAM;AAAA,IACZ,KAAK;AACD,aAAO,QAAQ,EAAE,QAAQ,IAAI,EAAE,SAAS,SAAS,IAAI,EAAE,SAAS,OAAO;AAAA,IAC3E,KAAK;AACD,aAAO,UAAU,EAAE,SAAS,QAAQ,EAAE,SAAS,SAAS;AAAA,IAC5D,KAAK;AACD,aAAO,WAAW,EAAE,SAAS,QAAQ,IAAI,EAAE,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,IACpE,KAAK;AACD,aAAO,YAAY,EAAE,YAAY,EAAE,IAAK,EAAE,SAAiB,cAAc,EAAE,IAAK,EAAE,SAAiB,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,IAC3I,KAAK;AACD,aAAO,cAAe,EAAE,SAAiB,MAAM,EAAE,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,EAC9E;AACJ;AAbS;;;AChET,SAAS,cAAc,MAAsB;AACzC,SAAO,KACF,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,yBAAyB,OAAO,EACxC,QAAQ,cAAc,GAAG,EACzB,KAAK;AACd;AANS;AAaF,SAAS,YAAY,OAAuB;AAC/C,QAAM,QAAQ,MACT,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,qCAAqC,EAAE,EAC/C,KAAK;AAGV,QAAM,WAAW,MAAM,MAAM,KAAK,EAC7B,IAAI,OAAK,cAAc,CAAC,CAAC,EACzB,KAAK,GAAG;AAEb,QAAM,QAAQ,SAAS,MAAM,KAAK,EAAE,OAAO,OAAK,EAAE,SAAS,CAAC;AAC5D,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SAAO,MAAM,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG;AAC5C;AAfgB;AAqBT,SAAS,cAAc,UAA0B;AACpD,QAAM,MAAM,KAAK,IAAI,QAAQ;AAC7B,SAAO,KAAO,IAAM,KAAK,IAAI,QAAQ,MAAM,EAAE;AACjD;AAHgB;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/rerankers/qwen3-reranker.ts"],"sourcesContent":["/**\n * BrainBank — Qwen3 Local Reranker\n * \n * Cross-encoder reranker using Qwen3-Reranker-0.6B via node-llama-cpp.\n * Auto-downloads the GGUF model from HuggingFace (~640MB, cached).\n * \n * Based on QMD's reranker architecture:\n * - Lazy model loading (loads on first rank() call)\n * - Flash attention for 20× less VRAM\n * - Document deduplication (identical texts scored once)\n * - Tokenizer-based truncation for oversized documents\n */\n\nimport type { Reranker } from '@/types.ts';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync, mkdirSync } from 'node:fs';\n\n// Default model — Qwen3-Reranker-0.6B quantized to Q8_0 (~640MB)\nconst DEFAULT_MODEL_URI = 'hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf';\n\n// Context size for reranking (Qwen3 template ~200 tokens overhead + query + doc)\nconst CONTEXT_SIZE = 2048;\n\n// Cache directory for downloaded models\nconst MODEL_CACHE_DIR = join(homedir(), '.cache', 'brainbank', 'models');\n\nexport interface Qwen3RerankerOptions {\n /** HuggingFace model URI. Default: Qwen3-Reranker-0.6B-Q8_0 */\n modelUri?: string;\n /** Model cache directory. Default: ~/.cache/brainbank/models/ */\n cacheDir?: string;\n /** Context size for ranking. Default: 2048 */\n contextSize?: number;\n}\n\nexport class Qwen3Reranker implements Reranker {\n private readonly _modelUri: string;\n private readonly _cacheDir: string;\n private readonly _contextSize: number;\n\n private _model: any = null;\n private _context: any = null;\n private _loadPromise: Promise<void> | null = null;\n\n constructor(options: Qwen3RerankerOptions = {}) {\n this._modelUri = options.modelUri ?? DEFAULT_MODEL_URI;\n this._cacheDir = options.cacheDir ?? MODEL_CACHE_DIR;\n this._contextSize = options.contextSize ?? CONTEXT_SIZE;\n }\n\n /**\n * Lazy-load the reranker model and create a ranking context.\n * Model is auto-downloaded from HuggingFace on first use.\n */\n private async _ensureLoaded(): Promise<void> {\n if (this._context) return;\n if (this._loadPromise) {\n await this._loadPromise;\n return;\n }\n\n this._loadPromise = (async () => {\n try {\n // Dynamic import — node-llama-cpp is an optional peer dependency.\n // String indirection prevents DTS from resolving the specifier at build time.\n const llamaModule = 'node-llama-cpp';\n const { getLlama, resolveModelFile } = await import(/* webpackIgnore: true */ llamaModule);\n\n // Ensure cache directory exists\n if (!existsSync(this._cacheDir)) {\n mkdirSync(this._cacheDir, { recursive: true });\n }\n\n // Download model if needed (resolveModelFile handles caching)\n const modelPath = await resolveModelFile(this._modelUri, this._cacheDir);\n\n // Initialize llama engine\n const llama = await getLlama();\n\n // Load model\n this._model = await llama.loadModel({ modelPath });\n\n // Create ranking context with flash attention for lower VRAM\n try {\n this._context = await this._model.createRankingContext({\n contextSize: this._contextSize,\n flashAttention: true,\n });\n } catch {\n // Flash attention might not be supported — retry without it\n this._context = await this._model.createRankingContext({\n contextSize: this._contextSize,\n });\n }\n } finally {\n this._loadPromise = null;\n }\n })();\n\n await this._loadPromise;\n }\n\n /**\n * Score each document's relevance to the query.\n * Returns scores (0.0 - 1.0) in same order as input documents.\n * \n * Deduplicates identical documents to avoid redundant computation.\n */\n async rank(query: string, documents: string[]): Promise<number[]> {\n if (documents.length === 0) return [];\n\n await this._ensureLoaded();\n\n // Deduplicate — identical texts get scored once\n const uniqueTexts = [...new Set(documents)];\n const textToScore = new Map<string, number>();\n\n // Truncate documents that exceed context size\n const truncated = uniqueTexts.map(text => {\n if (this._model) {\n const tokens = this._model.tokenize(text);\n // Budget: contextSize - ~200 overhead - query tokens\n const queryTokens = this._model.tokenize(query).length;\n const maxDocTokens = this._contextSize - 200 - queryTokens;\n if (tokens.length > maxDocTokens && maxDocTokens > 0) {\n return this._model.detokenize(tokens.slice(0, maxDocTokens));\n }\n }\n return text;\n });\n\n // Rank all unique documents at once\n const scores: number[] = await this._context.rankAll(query, truncated);\n\n // Map scores back\n for (let i = 0; i < uniqueTexts.length; i++) {\n textToScore.set(uniqueTexts[i], scores[i] ?? 0);\n }\n\n // Return scores in original document order\n return documents.map(doc => textToScore.get(doc) ?? 0);\n }\n\n /** Release model resources. */\n async close(): Promise<void> {\n if (this._context) {\n await this._context.dispose();\n this._context = null;\n }\n if (this._model) {\n await this._model.dispose?.();\n this._model = null;\n }\n }\n}\n"],"mappings":";;;;;AAcA,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,YAAY,iBAAiB;AAGtC,IAAM,oBAAoB;AAG1B,IAAM,eAAe;AAGrB,IAAM,kBAAkB,KAAK,QAAQ,GAAG,UAAU,aAAa,QAAQ;AAWhE,IAAM,gBAAN,MAAwC;AAAA,EApC/C,OAoC+C;AAAA;AAAA;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EAET,SAAc;AAAA,EACd,WAAgB;AAAA,EAChB,eAAqC;AAAA,EAE7C,YAAY,UAAgC,CAAC,GAAG;AAC5C,SAAK,YAAY,QAAQ,YAAY;AACrC,SAAK,YAAY,QAAQ,YAAY;AACrC,SAAK,eAAe,QAAQ,eAAe;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,gBAA+B;AACzC,QAAI,KAAK,SAAU;AACnB,QAAI,KAAK,cAAc;AACnB,YAAM,KAAK;AACX;AAAA,IACJ;AAEA,SAAK,gBAAgB,YAAY;AAC7B,UAAI;AAGA,cAAM,cAAc;AACpB,cAAM,EAAE,UAAU,iBAAiB,IAAI,MAAM;AAAA;AAAA,UAAiC;AAAA;AAG9E,YAAI,CAAC,WAAW,KAAK,SAAS,GAAG;AAC7B,oBAAU,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,QACjD;AAGA,cAAM,YAAY,MAAM,iBAAiB,KAAK,WAAW,KAAK,SAAS;AAGvE,cAAM,QAAQ,MAAM,SAAS;AAG7B,aAAK,SAAS,MAAM,MAAM,UAAU,EAAE,UAAU,CAAC;AAGjD,YAAI;AACA,eAAK,WAAW,MAAM,KAAK,OAAO,qBAAqB;AAAA,YACnD,aAAa,KAAK;AAAA,YAClB,gBAAgB;AAAA,UACpB,CAAC;AAAA,QACL,QAAQ;AAEJ,eAAK,WAAW,MAAM,KAAK,OAAO,qBAAqB;AAAA,YACnD,aAAa,KAAK;AAAA,UACtB,CAAC;AAAA,QACL;AAAA,MACJ,UAAE;AACE,aAAK,eAAe;AAAA,MACxB;AAAA,IACJ,GAAG;AAEH,UAAM,KAAK;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAK,OAAe,WAAwC;AAC9D,QAAI,UAAU,WAAW,EAAG,QAAO,CAAC;AAEpC,UAAM,KAAK,cAAc;AAGzB,UAAM,cAAc,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAC1C,UAAM,cAAc,oBAAI,IAAoB;AAG5C,UAAM,YAAY,YAAY,IAAI,UAAQ;AACtC,UAAI,KAAK,QAAQ;AACb,cAAM,SAAS,KAAK,OAAO,SAAS,IAAI;AAExC,cAAM,cAAc,KAAK,OAAO,SAAS,KAAK,EAAE;AAChD,cAAM,eAAe,KAAK,eAAe,MAAM;AAC/C,YAAI,OAAO,SAAS,gBAAgB,eAAe,GAAG;AAClD,iBAAO,KAAK,OAAO,WAAW,OAAO,MAAM,GAAG,YAAY,CAAC;AAAA,QAC/D;AAAA,MACJ;AACA,aAAO;AAAA,IACX,CAAC;AAGD,UAAM,SAAmB,MAAM,KAAK,SAAS,QAAQ,OAAO,SAAS;AAGrE,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AACzC,kBAAY,IAAI,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;AAAA,IAClD;AAGA,WAAO,UAAU,IAAI,SAAO,YAAY,IAAI,GAAG,KAAK,CAAC;AAAA,EACzD;AAAA;AAAA,EAGA,MAAM,QAAuB;AACzB,QAAI,KAAK,UAAU;AACf,YAAM,KAAK,SAAS,QAAQ;AAC5B,WAAK,WAAW;AAAA,IACpB;AACA,QAAI,KAAK,QAAQ;AACb,YAAM,KAAK,OAAO,UAAU;AAC5B,WAAK,SAAS;AAAA,IAClB;AAAA,EACJ;AACJ;","names":[]}
|
package/dist/code.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { E as EmbeddingProvider, P as Plugin } from './base-3SNc_CeY.js';
|
|
2
|
-
import 'better-sqlite3';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* BrainBank — Code Module
|
|
6
|
-
*
|
|
7
|
-
* Language-aware code indexing for 30+ languages.
|
|
8
|
-
*
|
|
9
|
-
* import { BrainBank } from 'brainbank';
|
|
10
|
-
* import { code } from 'brainbank/code';
|
|
11
|
-
*
|
|
12
|
-
* const brain = new BrainBank().use(code({ repoPath: '.' }));
|
|
13
|
-
*
|
|
14
|
-
* // Multi-repo: namespace to avoid key collisions
|
|
15
|
-
* brain
|
|
16
|
-
* .use(code({ repoPath: './frontend', name: 'code:frontend' }))
|
|
17
|
-
* .use(code({ repoPath: './backend', name: 'code:backend' }));
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
interface CodePluginOptions {
|
|
21
|
-
/** Repository path to index. Default: '.' */
|
|
22
|
-
repoPath?: string;
|
|
23
|
-
/** Maximum file size in bytes. Default: from config */
|
|
24
|
-
maxFileSize?: number;
|
|
25
|
-
/** Custom indexer name for multi-repo (e.g. 'code:frontend'). Default: 'code' */
|
|
26
|
-
name?: string;
|
|
27
|
-
/** Per-plugin embedding provider. Default: global embedding from BrainBank config. */
|
|
28
|
-
embeddingProvider?: EmbeddingProvider;
|
|
29
|
-
}
|
|
30
|
-
/** Create a code indexing plugin. */
|
|
31
|
-
declare function code(opts?: CodePluginOptions): Plugin;
|
|
32
|
-
|
|
33
|
-
export { type CodePluginOptions, code };
|
package/dist/code.js
DELETED
package/dist/docs.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { E as EmbeddingProvider, P as Plugin } from './base-3SNc_CeY.js';
|
|
2
|
-
import 'better-sqlite3';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* BrainBank — Docs Module
|
|
6
|
-
*
|
|
7
|
-
* Index any folder of markdown/text files (notes, docs, wikis).
|
|
8
|
-
* Heading-aware smart chunking inspired by qmd.
|
|
9
|
-
*
|
|
10
|
-
* import { docs } from 'brainbank/docs';
|
|
11
|
-
* brain.use(docs());
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
interface DocsPluginOptions {
|
|
15
|
-
/** Per-plugin embedding provider. Default: global embedding from BrainBank config. */
|
|
16
|
-
embeddingProvider?: EmbeddingProvider;
|
|
17
|
-
}
|
|
18
|
-
/** Create a document collections plugin. */
|
|
19
|
-
declare function docs(opts?: DocsPluginOptions): Plugin;
|
|
20
|
-
|
|
21
|
-
export { type DocsPluginOptions, docs };
|