brainbank 0.1.3 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +26 -12
  2. package/dist/{types-Da_zLLOl.d.ts → base-9vfWRHCV.d.ts} +131 -31
  3. package/dist/{chunk-TW5NTYYZ.js → chunk-6MFTQV3O.js} +909 -685
  4. package/dist/chunk-6MFTQV3O.js.map +1 -0
  5. package/dist/chunk-7JCEW7LT.js +266 -0
  6. package/dist/chunk-7JCEW7LT.js.map +1 -0
  7. package/dist/{chunk-GOUBW7UA.js → chunk-F6SJ3U4H.js} +98 -34
  8. package/dist/chunk-F6SJ3U4H.js.map +1 -0
  9. package/dist/{chunk-MJ3Y24H6.js → chunk-FJJY4H2Y.js} +11 -11
  10. package/dist/chunk-FJJY4H2Y.js.map +1 -0
  11. package/dist/{chunk-3GAIDXRW.js → chunk-GUT5MSJT.js} +5 -11
  12. package/dist/chunk-GUT5MSJT.js.map +1 -0
  13. package/dist/{chunk-2P3EGY6S.js → chunk-QNHBCOKB.js} +2 -2
  14. package/dist/chunk-QNHBCOKB.js.map +1 -0
  15. package/dist/{chunk-4ZKBQ33J.js → chunk-V4UJKXPK.js} +23 -5
  16. package/dist/chunk-V4UJKXPK.js.map +1 -0
  17. package/dist/{chunk-RAEBYV75.js → chunk-WR4WXKJT.js} +37 -23
  18. package/dist/chunk-WR4WXKJT.js.map +1 -0
  19. package/dist/{chunk-Z5SU54HP.js → chunk-X6645UVR.js} +3 -3
  20. package/dist/chunk-X6645UVR.js.map +1 -0
  21. package/dist/cli.js +122 -102
  22. package/dist/cli.js.map +1 -1
  23. package/dist/code.d.ts +5 -5
  24. package/dist/code.js +1 -1
  25. package/dist/docs.d.ts +4 -6
  26. package/dist/docs.js +1 -1
  27. package/dist/git.d.ts +5 -5
  28. package/dist/git.js +1 -1
  29. package/dist/index.d.ts +54 -90
  30. package/dist/index.js +13 -13
  31. package/dist/memory.d.ts +5 -7
  32. package/dist/memory.js +9 -12
  33. package/dist/memory.js.map +1 -1
  34. package/dist/notes.d.ts +4 -6
  35. package/dist/notes.js +7 -10
  36. package/dist/notes.js.map +1 -1
  37. package/dist/{openai-PCTYLOWI.js → openai-CYDMYX7X.js} +2 -2
  38. package/package.json +3 -3
  39. package/dist/chunk-2P3EGY6S.js.map +0 -1
  40. package/dist/chunk-3GAIDXRW.js.map +0 -1
  41. package/dist/chunk-4ZKBQ33J.js.map +0 -1
  42. package/dist/chunk-GOUBW7UA.js.map +0 -1
  43. package/dist/chunk-MJ3Y24H6.js.map +0 -1
  44. package/dist/chunk-N6ZMBFDE.js +0 -224
  45. package/dist/chunk-N6ZMBFDE.js.map +0 -1
  46. package/dist/chunk-RAEBYV75.js.map +0 -1
  47. package/dist/chunk-TW5NTYYZ.js.map +0 -1
  48. package/dist/chunk-Z5SU54HP.js.map +0 -1
  49. /package/dist/{openai-PCTYLOWI.js.map → openai-CYDMYX7X.js.map} +0 -0
@@ -1,11 +1,13 @@
1
1
  import {
2
- reciprocalRankFusion
3
- } from "./chunk-4ZKBQ33J.js";
2
+ normalizeBM25,
3
+ reciprocalRankFusion,
4
+ sanitizeFTS
5
+ } from "./chunk-V4UJKXPK.js";
4
6
  import {
5
7
  __name
6
8
  } from "./chunk-7QVYU63E.js";
7
9
 
8
- // src/memory/note-store.ts
10
+ // src/indexers/notes/note-store.ts
9
11
  var NoteStore = class {
10
12
  static {
11
13
  __name(this, "NoteStore");
@@ -70,8 +72,8 @@ ${patterns.join(". ")}`;
70
72
  ]);
71
73
  const fusedResults = reciprocalRankFusion(
72
74
  [
73
- vectorHits.map((m) => ({ type: "pattern", score: m.score ?? 0, content: m.summary, metadata: { id: m.id } })),
74
- bm25Hits.map((m) => ({ type: "pattern", score: m.score ?? 0, content: m.summary, metadata: { id: m.id } }))
75
+ vectorHits.map((m) => ({ type: "collection", score: m.score ?? 0, content: m.summary, metadata: { id: m.id } })),
76
+ bm25Hits.map((m) => ({ type: "collection", score: m.score ?? 0, content: m.summary, metadata: { id: m.id } }))
75
77
  ]
76
78
  );
77
79
  const allById = /* @__PURE__ */ new Map();
@@ -142,10 +144,8 @@ ${patterns.join(". ")}`;
142
144
  }));
143
145
  }
144
146
  _searchBM25(query, k) {
145
- const clean = query.replace(/[{}[\]()^~*:]/g, " ").replace(/\bAND\b|\bOR\b|\bNOT\b|\bNEAR\b/gi, "").trim();
146
- const words = clean.split(/\s+/).filter((w) => w.length > 1);
147
- if (words.length === 0) return [];
148
- const ftsQuery = words.map((w) => `"${w}"`).join(" ");
147
+ const ftsQuery = sanitizeFTS(query);
148
+ if (!ftsQuery) return [];
149
149
  try {
150
150
  const rows = this._db.prepare(`
151
151
  SELECT m.*, bm25(fts_notes, 5.0, 3.0, 2.0, 2.0, 1.0) AS score
@@ -157,7 +157,7 @@ ${patterns.join(". ")}`;
157
157
  `).all(ftsQuery, k);
158
158
  return rows.map((r) => ({
159
159
  ...this._rowToNote(r),
160
- score: 1 / (1 + Math.exp(-0.3 * (Math.abs(r.score) - 5)))
160
+ score: normalizeBM25(r.score)
161
161
  }));
162
162
  } catch {
163
163
  return [];
@@ -182,4 +182,4 @@ ${patterns.join(". ")}`;
182
182
  export {
183
183
  NoteStore
184
184
  };
185
- //# sourceMappingURL=chunk-MJ3Y24H6.js.map
185
+ //# sourceMappingURL=chunk-FJJY4H2Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/indexers/notes/note-store.ts"],"sourcesContent":["/**\n * BrainBank — Note Memory Store\n * \n * Stores structured note digests for long-term agent memory.\n * Each digest captures decisions, files changed, patterns, and open questions.\n * Supports vector + BM25 hybrid retrieval via HNSW + FTS5.\n * \n * Memory tiers:\n * - \"short\" (default): Full digest, last ~20 notes\n * - \"long\": Compressed to patterns + decisions only\n */\n\nimport type { Database } from '../../db/database.ts';\nimport type { EmbeddingProvider, SearchResult } from '../../types.ts';\nimport type { HNSWIndex } from '../../providers/vector/hnsw.ts';\nimport { BM25Search } from '../../search/keyword/bm25.ts';\nimport { reciprocalRankFusion } from '../../search/rrf.ts';\nimport { sanitizeFTS, normalizeBM25 } from '../../search/keyword/utils.ts';\n\nexport interface NoteDigest {\n title: string;\n summary: string;\n decisions?: string[];\n filesChanged?: string[];\n patterns?: string[];\n openQuestions?: string[];\n tags?: string[];\n}\n\nexport interface StoredNote extends NoteDigest {\n id: number;\n tier: 'short' | 'long';\n createdAt: number;\n score?: number;\n}\n\nexport interface RecallOptions {\n /** Max results. Default: 5 */\n k?: number;\n /** Search mode. Default: 'hybrid' */\n mode?: 'hybrid' | 'vector' | 'keyword';\n /** Minimum score threshold. Default: 0.15 */\n minScore?: number;\n /** Filter by tier. Default: all */\n tier?: 'short' | 'long';\n}\n\nexport class NoteStore {\n private _db: Database;\n private _embedding: EmbeddingProvider;\n private _hnsw: HNSWIndex;\n private _vecs: Map<number, Float32Array>;\n\n constructor(\n db: Database,\n embedding: EmbeddingProvider,\n hnsw: HNSWIndex,\n vecs: Map<number, Float32Array>,\n ) {\n this._db = db;\n this._embedding = embedding;\n this._hnsw = hnsw;\n this._vecs = vecs;\n }\n\n /**\n * Store a note digest.\n * Embeds title + summary for vector search, auto-indexed in FTS5.\n */\n async remember(digest: NoteDigest): Promise<number> {\n const { title, summary, decisions = [], filesChanged = [], patterns = [], openQuestions = [], tags = [] } = digest;\n\n // Store in SQLite\n const result = this._db.prepare(`\n INSERT INTO note_memories (title, summary, decisions_json, files_json, patterns_json, open_json, tags_json)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `).run(\n title,\n summary,\n JSON.stringify(decisions),\n JSON.stringify(filesChanged),\n JSON.stringify(patterns),\n JSON.stringify(openQuestions),\n JSON.stringify(tags),\n );\n\n const id = Number(result.lastInsertRowid);\n\n // Embed and index\n const text = `${title}\\n${summary}\\n${decisions.join('. ')}\\n${patterns.join('. ')}`;\n const vec = await this._embedding.embed(text);\n\n this._db.prepare('INSERT INTO note_vectors (note_id, embedding) VALUES (?, ?)').run(\n id, Buffer.from(vec.buffer),\n );\n\n this._hnsw.add(vec, id);\n this._vecs.set(id, vec);\n\n return id;\n }\n\n /**\n * Recall relevant notes.\n * Supports vector, keyword, or hybrid (default) retrieval.\n */\n async recall(query: string, options: RecallOptions = {}): Promise<StoredNote[]> {\n const { k = 5, mode = 'hybrid', minScore = 0.15, tier } = options;\n\n let results: StoredNote[];\n\n if (mode === 'keyword') {\n results = this._searchBM25(query, k);\n } else if (mode === 'vector') {\n results = await this._searchVector(query, k);\n } else {\n // Hybrid: vector + BM25 → RRF\n const [vectorHits, bm25Hits] = await Promise.all([\n this._searchVector(query, k),\n Promise.resolve(this._searchBM25(query, k)),\n ]);\n\n const fusedResults = reciprocalRankFusion(\n [\n vectorHits.map(m => ({ type: 'collection' as const, score: m.score ?? 0, content: m.summary, metadata: { id: m.id } })),\n bm25Hits.map(m => ({ type: 'collection' as const, score: m.score ?? 0, content: m.summary, metadata: { id: m.id } })),\n ],\n );\n\n // Map back to full StoredNote objects\n const allById = new Map<number, StoredNote>();\n for (const m of [...vectorHits, ...bm25Hits]) allById.set(m.id, m);\n\n results = fusedResults\n .map(r => {\n const mem = allById.get((r.metadata as any).id);\n if (!mem) return null;\n return { ...mem, score: r.score };\n })\n .filter(Boolean) as StoredNote[];\n }\n\n // Apply filters\n return results\n .filter(m => (m.score ?? 0) >= minScore)\n .filter(m => !tier || m.tier === tier)\n .slice(0, k);\n }\n\n /**\n * List recent notes.\n */\n list(limit: number = 20, tier?: 'short' | 'long'): StoredNote[] {\n const sql = tier\n ? 'SELECT * FROM note_memories WHERE tier = ? ORDER BY id DESC LIMIT ?'\n : 'SELECT * FROM note_memories ORDER BY id DESC LIMIT ?';\n\n const rows = tier\n ? this._db.prepare(sql).all(tier, limit) as any[]\n : this._db.prepare(sql).all(limit) as any[];\n\n return rows.map(r => this._rowToNote(r));\n }\n\n /**\n * Get total count of notes.\n */\n count(): { total: number; short: number; long: number } {\n const total = (this._db.prepare('SELECT COUNT(*) as n FROM note_memories').get() as any).n;\n const short = (this._db.prepare(\"SELECT COUNT(*) as n FROM note_memories WHERE tier = 'short'\").get() as any).n;\n const long = (this._db.prepare(\"SELECT COUNT(*) as n FROM note_memories WHERE tier = 'long'\").get() as any).n;\n return { total, short, long };\n }\n\n /**\n * Consolidate old short-term notes into long-term.\n * Keeps the most recent `keepRecent` as short-term, compresses the rest.\n */\n consolidate(keepRecent: number = 20): { promoted: number } {\n // Find short-term notes beyond the keep window\n const old = this._db.prepare(`\n SELECT id FROM note_memories \n WHERE tier = 'short' \n ORDER BY created_at DESC \n LIMIT -1 OFFSET ?\n `).all(keepRecent) as any[];\n\n if (old.length === 0) return { promoted: 0 };\n\n const ids = old.map((r: any) => r.id);\n const placeholders = ids.map(() => '?').join(',');\n\n // Promote to long-term: clear verbose fields, keep patterns + decisions\n this._db.prepare(`\n UPDATE note_memories \n SET tier = 'long',\n open_json = '[]',\n files_json = '[]'\n WHERE id IN (${placeholders})\n `).run(...ids);\n\n return { promoted: ids.length };\n }\n\n // ── Private helpers ────────────────────────────\n\n private async _searchVector(query: string, k: number): Promise<StoredNote[]> {\n if (this._hnsw.size === 0) return [];\n\n const queryVec = await this._embedding.embed(query);\n const hits = this._hnsw.search(queryVec, k);\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 const placeholders = ids.map(() => '?').join(',');\n\n const rows = this._db.prepare(\n `SELECT * FROM note_memories WHERE id IN (${placeholders})`\n ).all(...ids) as any[];\n\n return rows.map(r => ({\n ...this._rowToNote(r),\n score: scoreMap.get(r.id) ?? 0,\n }));\n }\n\n private _searchBM25(query: string, k: number): StoredNote[] {\n const ftsQuery = sanitizeFTS(query);\n if (!ftsQuery) return [];\n\n try {\n const rows = this._db.prepare(`\n SELECT m.*, bm25(fts_notes, 5.0, 3.0, 2.0, 2.0, 1.0) AS score\n FROM fts_notes f\n JOIN note_memories m ON m.id = f.rowid\n WHERE fts_notes MATCH ?\n ORDER BY score ASC\n LIMIT ?\n `).all(ftsQuery, k) as any[];\n\n return rows.map(r => ({\n ...this._rowToNote(r),\n score: normalizeBM25(r.score),\n }));\n } catch {\n return [];\n }\n }\n\n private _rowToNote(r: any): StoredNote {\n return {\n id: r.id,\n title: r.title,\n summary: r.summary,\n decisions: JSON.parse(r.decisions_json || '[]'),\n filesChanged: JSON.parse(r.files_json || '[]'),\n patterns: JSON.parse(r.patterns_json || '[]'),\n openQuestions: JSON.parse(r.open_json || '[]'),\n tags: JSON.parse(r.tags_json || '[]'),\n tier: r.tier,\n createdAt: r.created_at,\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  __name
3
3
  } from "./chunk-7QVYU63E.js";
4
4
 
5
- // src/embeddings/openai.ts
5
+ // src/providers/embeddings/openai.ts
6
6
  var DEFAULT_MODEL = "text-embedding-3-small";
7
7
  var DEFAULT_DIMS = {
8
8
  "text-embedding-3-small": 1536,
@@ -20,7 +20,6 @@ var OpenAIEmbedding = class {
20
20
  _model;
21
21
  _baseUrl;
22
22
  _requestDims;
23
- _retrying = false;
24
23
  constructor(options = {}) {
25
24
  this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
26
25
  this._model = options.model ?? DEFAULT_MODEL;
@@ -51,7 +50,7 @@ var OpenAIEmbedding = class {
51
50
  _isTokenLimitError(errText) {
52
51
  return errText.includes("maximum input length") || errText.includes("maximum context length") || errText.includes("too many tokens");
53
52
  }
54
- async _request(input) {
53
+ async _request(input, retryDepth = 0) {
55
54
  if (!this._apiKey) {
56
55
  throw new Error("OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.");
57
56
  }
@@ -83,13 +82,8 @@ var OpenAIEmbedding = class {
83
82
  }
84
83
  return results;
85
84
  }
86
- if (isTokenLimit && safeInput.length === 1 && !this._retrying) {
87
- this._retrying = true;
88
- try {
89
- return await this._request([safeInput[0].slice(0, 6e3)]);
90
- } finally {
91
- this._retrying = false;
92
- }
85
+ if (isTokenLimit && safeInput.length === 1 && retryDepth < 1) {
86
+ return await this._request([safeInput[0].slice(0, 6e3)], retryDepth + 1);
93
87
  }
94
88
  throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);
95
89
  }
@@ -102,4 +96,4 @@ var OpenAIEmbedding = class {
102
96
  export {
103
97
  OpenAIEmbedding
104
98
  };
105
- //# sourceMappingURL=chunk-3GAIDXRW.js.map
99
+ //# sourceMappingURL=chunk-GUT5MSJT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/embeddings/openai.ts"],"sourcesContent":["/**\n * BrainBank — OpenAI Embedding Provider\n * \n * Uses OpenAI's embedding API via fetch (no SDK dependency).\n * Supports text-embedding-3-small, text-embedding-3-large, and ada-002.\n * \n * Usage:\n * const brain = new BrainBank({\n * embeddingProvider: new OpenAIEmbedding({ model: 'text-embedding-3-small' }),\n * });\n */\n\nimport type { EmbeddingProvider } from '../../types.ts';\n\nconst DEFAULT_MODEL = 'text-embedding-3-small';\nconst DEFAULT_DIMS: Record<string, number> = {\n 'text-embedding-3-small': 1536,\n 'text-embedding-3-large': 3072,\n 'text-embedding-ada-002': 1536,\n};\nconst API_URL = 'https://api.openai.com/v1/embeddings';\nconst MAX_BATCH = 100; // OpenAI limit per request\n\nexport interface OpenAIEmbeddingOptions {\n /** OpenAI API key. Falls back to OPENAI_API_KEY env var. */\n apiKey?: string;\n /** Model name. Default: 'text-embedding-3-small' */\n model?: string;\n /** Vector dimensions. If omitted, uses model default. text-embedding-3-* supports custom dims. */\n dims?: number;\n /** Base URL override (for Azure, proxies, etc.) */\n baseUrl?: string;\n}\n\nexport class OpenAIEmbedding implements EmbeddingProvider {\n readonly dims: number;\n\n private _apiKey: string;\n private _model: string;\n private _baseUrl: string;\n private _requestDims: number | undefined;\n\n constructor(options: OpenAIEmbeddingOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n this._baseUrl = options.baseUrl ?? API_URL;\n\n // Custom dims only supported by text-embedding-3-*\n if (options.dims && this._model.startsWith('text-embedding-3')) {\n this._requestDims = options.dims;\n this.dims = options.dims;\n } else {\n this.dims = options.dims ?? DEFAULT_DIMS[this._model] ?? 1536;\n }\n }\n\n async embed(text: string): Promise<Float32Array> {\n const results = await this._request([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return [];\n\n const results: Float32Array[] = [];\n\n // Split into chunks of MAX_BATCH\n for (let i = 0; i < texts.length; i += MAX_BATCH) {\n const batch = texts.slice(i, i + MAX_BATCH);\n const embeddings = await this._request(batch);\n results.push(...embeddings);\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n // No resources to release\n }\n\n private _isTokenLimitError(errText: string): boolean {\n return errText.includes('maximum input length') ||\n errText.includes('maximum context length') ||\n errText.includes('too many tokens');\n }\n\n private async _request(input: string[], retryDepth: number = 0): Promise<Float32Array[]> {\n if (!this._apiKey) {\n throw new Error('OpenAI API key required. Set OPENAI_API_KEY env var or pass apiKey option.');\n }\n\n // Truncate texts that would exceed token limit (~4 chars per token, 8192 max)\n const MAX_CHARS = 24_000;\n const safeInput = input.map(t => t.length > MAX_CHARS ? t.slice(0, MAX_CHARS) : t);\n\n const body: Record<string, any> = {\n model: this._model,\n input: safeInput,\n };\n\n if (this._requestDims) {\n body.dimensions = this._requestDims;\n }\n\n const res = await fetch(this._baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const err = await res.text();\n const isTokenLimit = res.status === 400 && this._isTokenLimitError(err);\n\n // If token limit error in a batch, retry each item individually with more aggressive truncation\n if (isTokenLimit && safeInput.length > 1) {\n const results: Float32Array[] = [];\n for (const text of safeInput) {\n const r = await this._request([text.slice(0, 8_000)]);\n results.push(r[0]);\n }\n return results;\n }\n // Last resort: if single item still fails, truncate to ~2k tokens (max 1 retry)\n if (isTokenLimit && safeInput.length === 1 && retryDepth < 1) {\n return await this._request([safeInput[0].slice(0, 6_000)], retryDepth + 1);\n }\n throw new Error(`OpenAI embedding API error (${res.status}): ${err}`);\n }\n\n const json = await res.json() as {\n data: Array<{ embedding: number[]; index: number }>;\n };\n\n // Sort by index (API may return out of order)\n const sorted = json.data.sort((a, b) => a.index - b.index);\n\n return sorted.map(d => new Float32Array(d.embedding));\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  __name
3
3
  } from "./chunk-7QVYU63E.js";
4
4
 
5
- // src/embeddings/math.ts
5
+ // src/lib/math.ts
6
6
  function cosineSimilarity(a, b) {
7
7
  if (a.length !== b.length) {
8
8
  throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
@@ -34,4 +34,4 @@ export {
34
34
  cosineSimilarity,
35
35
  normalize
36
36
  };
37
- //# sourceMappingURL=chunk-2P3EGY6S.js.map
37
+ //# sourceMappingURL=chunk-QNHBCOKB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/math.ts"],"sourcesContent":["/**\n * BrainBank — Math Utilities\n * \n * Pure vector math functions for similarity calculations.\n * No dependencies — works on Float32Array directly.\n */\n\n/**\n * Cosine similarity between two vectors.\n * Assumes vectors are already normalized (unit length).\n * Returns value between -1.0 and 1.0.\n */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n if (a.length === 0) return 0;\n\n let dot = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n }\n return dot;\n}\n\n/**\n * Full cosine similarity (normalizes first).\n * Use this when vectors may not be pre-normalized.\n */\nexport function cosineSimilarityFull(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n if (a.length === 0) return 0;\n\n let dot = 0, normA = 0, normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/**\n * L2-normalize a vector to unit length.\n * Returns a new Float32Array.\n */\nexport function normalize(vec: Float32Array): Float32Array {\n let norm = 0;\n for (let i = 0; i < vec.length; i++) {\n norm += vec[i] * vec[i];\n }\n norm = Math.sqrt(norm);\n if (norm === 0) return new Float32Array(vec.length);\n\n const result = new Float32Array(vec.length);\n for (let i = 0; i < vec.length; i++) {\n result[i] = vec[i] / norm;\n }\n return result;\n}\n\n/**\n * Euclidean distance between two vectors.\n */\nexport function euclideanDistance(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) {\n throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n }\n let sum = 0;\n for (let i = 0; i < a.length; i++) {\n const d = a[i] - b[i];\n sum += d * d;\n }\n return Math.sqrt(sum);\n}\n"],"mappings":";;;;;AAYO,SAAS,iBAAiB,GAAiB,GAAyB;AACvE,MAAI,EAAE,WAAW,EAAE,QAAQ;AACvB,UAAM,IAAI,MAAM,8BAA8B,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE;AAAA,EAC3E;AACA,MAAI,EAAE,WAAW,EAAG,QAAO;AAE3B,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,WAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,EACrB;AACA,SAAO;AACX;AAXgB;AAqCT,SAAS,UAAU,KAAiC;AACvD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,YAAQ,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,EAC1B;AACA,SAAO,KAAK,KAAK,IAAI;AACrB,MAAI,SAAS,EAAG,QAAO,IAAI,aAAa,IAAI,MAAM;AAElD,QAAM,SAAS,IAAI,aAAa,IAAI,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,WAAO,CAAC,IAAI,IAAI,CAAC,IAAI;AAAA,EACzB;AACA,SAAO;AACX;AAbgB;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  __name
3
3
  } from "./chunk-7QVYU63E.js";
4
4
 
5
- // src/query/rrf.ts
5
+ // src/search/rrf.ts
6
6
  function reciprocalRankFusion(resultSets, k = 60, maxResults = 15) {
7
7
  const fused = /* @__PURE__ */ new Map();
8
8
  for (const results of resultSets) {
@@ -44,13 +44,31 @@ function resultKey(r) {
44
44
  return `commit:${r.metadata.hash || r.metadata.shortHash}`;
45
45
  case "pattern":
46
46
  return `pattern:${r.metadata.taskType}:${r.content?.slice(0, 60)}`;
47
- default:
48
- return `${r.type}:${r.content?.slice(0, 80)}`;
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)}`;
49
51
  }
50
52
  }
51
53
  __name(resultKey, "resultKey");
52
54
 
55
+ // src/search/keyword/utils.ts
56
+ function sanitizeFTS(query) {
57
+ const clean = query.replace(/[{}[\]()^~*:]/g, " ").replace(/\bAND\b|\bOR\b|\bNOT\b|\bNEAR\b/gi, "").trim();
58
+ const words = clean.split(/\s+/).filter((w) => w.length > 1);
59
+ if (words.length === 0) return "";
60
+ return words.map((w) => `"${w}"`).join(" ");
61
+ }
62
+ __name(sanitizeFTS, "sanitizeFTS");
63
+ function normalizeBM25(rawScore) {
64
+ const abs = Math.abs(rawScore);
65
+ return 1 / (1 + Math.exp(-0.3 * (abs - 5)));
66
+ }
67
+ __name(normalizeBM25, "normalizeBM25");
68
+
53
69
  export {
54
- reciprocalRankFusion
70
+ reciprocalRankFusion,
71
+ sanitizeFTS,
72
+ normalizeBM25
55
73
  };
56
- //# sourceMappingURL=chunk-4ZKBQ33J.js.map
74
+ //# sourceMappingURL=chunk-V4UJKXPK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/search/rrf.ts","../src/search/keyword/utils.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 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 * Sanitize a user query for FTS5 syntax.\n * Strips operators that would cause parse errors and converts\n * 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 const words = clean.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;AAGxB,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;AA7CgB;AAkDhB,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;;;AC7DF,SAAS,YAAY,OAAuB;AAC/C,QAAM,QAAQ,MACT,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,qCAAqC,EAAE,EAC/C,KAAK;AAEV,QAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,OAAO,OAAK,EAAE,SAAS,CAAC;AACzD,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SAAO,MAAM,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG;AAC5C;AAVgB;AAgBT,SAAS,cAAc,UAA0B;AACpD,QAAM,MAAM,KAAK,IAAI,QAAQ;AAC7B,SAAO,KAAO,IAAM,KAAK,IAAI,QAAQ,MAAM,EAAE;AACjD;AAHgB;","names":[]}
@@ -2,11 +2,14 @@ import {
2
2
  __name
3
3
  } from "./chunk-7QVYU63E.js";
4
4
 
5
- // src/indexers/code-indexer.ts
5
+ // src/indexers/code/code-indexer.ts
6
6
  import fs from "fs";
7
7
  import path2 from "path";
8
8
 
9
- // src/indexers/chunker.ts
9
+ // src/indexers/code/code-chunker.ts
10
+ import { createRequire as createRequire2 } from "module";
11
+
12
+ // src/indexers/code/grammars.ts
10
13
  import { createRequire } from "module";
11
14
  var require2 = createRequire(import.meta.url);
12
15
  function tryGrammar(pkg, nodeTypes, accessor) {
@@ -102,6 +105,9 @@ var GRAMMARS = {
102
105
  method: ["method_declaration"]
103
106
  })
104
107
  };
108
+
109
+ // src/indexers/code/code-chunker.ts
110
+ var require3 = createRequire2(import.meta.url);
105
111
  var CodeChunker = class {
106
112
  static {
107
113
  __name(this, "CodeChunker");
@@ -120,7 +126,7 @@ var CodeChunker = class {
120
126
  _ensureParser() {
121
127
  if (!this._parser) {
122
128
  try {
123
- const Parser = require2("tree-sitter");
129
+ const Parser = require3("tree-sitter");
124
130
  this._parser = new Parser();
125
131
  } catch {
126
132
  this._parser = false;
@@ -369,7 +375,7 @@ var CodeChunker = class {
369
375
  // ── Fallback: Generic sliding window ────────────
370
376
  _chunkGeneric(filePath, lines, language) {
371
377
  const chunks = [];
372
- const step = this.MAX - this.OVERLAP;
378
+ const step = Math.max(this.MAX - this.OVERLAP, 1);
373
379
  for (let s = 0; s < lines.length; s += step) {
374
380
  const e = Math.min(s + this.MAX, lines.length);
375
381
  const content = lines.slice(s, e).join("\n").trim();
@@ -390,7 +396,7 @@ var CodeChunker = class {
390
396
  /** Split a large block into overlapping sub-chunks. */
391
397
  _splitLargeBlock(filePath, lines, start, end, name, type, language) {
392
398
  const chunks = [];
393
- const step = this.MAX - this.OVERLAP;
399
+ const step = Math.max(this.MAX - this.OVERLAP, 1);
394
400
  let part = 1;
395
401
  for (let s = start; s <= end; s += step) {
396
402
  const e = Math.min(s + this.MAX, end + 1);
@@ -537,7 +543,7 @@ function getLanguage(filePath) {
537
543
  }
538
544
  __name(getLanguage, "getLanguage");
539
545
  function isIgnoredDir(dirName) {
540
- return IGNORE_DIRS.has(dirName) || dirName.startsWith(".");
546
+ return IGNORE_DIRS.has(dirName);
541
547
  }
542
548
  __name(isIgnoredDir, "isIgnoredDir");
543
549
  function isIgnoredFile(fileName) {
@@ -545,7 +551,7 @@ function isIgnoredFile(fileName) {
545
551
  }
546
552
  __name(isIgnoredFile, "isIgnoredFile");
547
553
 
548
- // src/indexers/code-indexer.ts
554
+ // src/indexers/code/code-indexer.ts
549
555
  var CodeIndexer = class {
550
556
  static {
551
557
  __name(this, "CodeIndexer");
@@ -585,19 +591,27 @@ var CodeIndexer = class {
585
591
  skipped++;
586
592
  continue;
587
593
  }
588
- if (existing) {
594
+ const oldChunks = this._deps.db.prepare(
595
+ "SELECT id FROM code_chunks WHERE file_path = ?"
596
+ ).all(rel);
597
+ if (oldChunks.length > 0) {
598
+ for (const { id } of oldChunks) {
599
+ this._deps.hnsw.remove(id);
600
+ this._deps.vectorCache.delete(id);
601
+ }
589
602
  this._deps.db.prepare("DELETE FROM code_chunks WHERE file_path = ?").run(rel);
590
603
  }
591
604
  const ext = path2.extname(filePath).toLowerCase();
592
605
  const language = SUPPORTED_EXTENSIONS[ext] ?? "text";
593
606
  const chunks = await this._chunker.chunk(rel, content, language);
594
- for (const chunk of chunks) {
595
- const text = [
596
- `File: ${rel}`,
597
- chunk.name ? `${chunk.chunkType}: ${chunk.name}` : chunk.chunkType,
598
- chunk.content
599
- ].join("\n");
600
- const vec = await this._deps.embedding.embed(text);
607
+ const embeddingTexts = chunks.map((chunk) => [
608
+ `File: ${rel}`,
609
+ chunk.name ? `${chunk.chunkType}: ${chunk.name}` : chunk.chunkType,
610
+ chunk.content
611
+ ].join("\n"));
612
+ const vecs = await this._deps.embedding.embedBatch(embeddingTexts);
613
+ for (let ci = 0; ci < chunks.length; ci++) {
614
+ const chunk = chunks[ci];
601
615
  const result = this._deps.db.prepare(
602
616
  `INSERT INTO code_chunks (file_path, chunk_type, name, start_line, end_line, content, language, file_hash)
603
617
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
@@ -605,9 +619,9 @@ var CodeIndexer = class {
605
619
  const id = Number(result.lastInsertRowid);
606
620
  this._deps.db.prepare(
607
621
  "INSERT INTO code_vectors (chunk_id, embedding) VALUES (?, ?)"
608
- ).run(id, Buffer.from(vec.buffer));
609
- this._deps.hnsw.add(vec, id);
610
- this._deps.vectorCache.set(id, vec);
622
+ ).run(id, Buffer.from(vecs[ci].buffer));
623
+ this._deps.hnsw.add(vecs[ci], id);
624
+ this._deps.vectorCache.set(id, vecs[ci]);
611
625
  totalChunks++;
612
626
  }
613
627
  this._deps.db.prepare(
@@ -655,14 +669,14 @@ var CodeIndexer = class {
655
669
  }
656
670
  };
657
671
 
658
- // src/plugins/code.ts
659
- var CodeModuleImpl = class {
672
+ // src/indexers/code/code-plugin.ts
673
+ var CodePlugin = class {
660
674
  constructor(opts = {}) {
661
675
  this.opts = opts;
662
676
  this.name = opts.name ?? "code";
663
677
  }
664
678
  static {
665
- __name(this, "CodeModuleImpl");
679
+ __name(this, "CodePlugin");
666
680
  }
667
681
  name;
668
682
  hnsw;
@@ -691,7 +705,7 @@ var CodeModuleImpl = class {
691
705
  }
692
706
  };
693
707
  function code(opts) {
694
- return new CodeModuleImpl(opts);
708
+ return new CodePlugin(opts);
695
709
  }
696
710
  __name(code, "code");
697
711
 
@@ -706,4 +720,4 @@ export {
706
720
  CodeIndexer,
707
721
  code
708
722
  };
709
- //# sourceMappingURL=chunk-RAEBYV75.js.map
723
+ //# sourceMappingURL=chunk-WR4WXKJT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/indexers/code/code-indexer.ts","../src/indexers/code/code-chunker.ts","../src/indexers/code/grammars.ts","../src/indexers/languages.ts","../src/indexers/code/code-plugin.ts"],"sourcesContent":["/**\n * BrainBank — Code Indexer\n * \n * Walks a repository, chunks source files semantically,\n * embeds each chunk, and stores in SQLite + HNSW.\n * Incremental: only re-indexes files that changed (by content hash).\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { CodeChunker } from './code-chunker.ts';\nimport { SUPPORTED_EXTENSIONS, IGNORE_DIRS, isIgnoredDir, isIgnoredFile } from '../languages.ts';\nimport type { Database } from '../../db/database.ts';\nimport type { EmbeddingProvider, ProgressCallback, IndexResult } from '../../types.ts';\nimport type { HNSWIndex } from '../../providers/vector/hnsw.ts';\n\nexport interface CodeIndexerDeps {\n db: Database;\n hnsw: HNSWIndex;\n vectorCache: Map<number, Float32Array>;\n embedding: EmbeddingProvider;\n}\n\nexport interface CodeIndexOptions {\n forceReindex?: boolean;\n onProgress?: ProgressCallback;\n}\n\nexport class CodeIndexer {\n private _chunker = new CodeChunker();\n private _deps: CodeIndexerDeps;\n private _repoPath: string;\n private _maxFileSize: number;\n\n constructor(repoPath: string, deps: CodeIndexerDeps, maxFileSize: number = 512_000) {\n this._deps = deps;\n this._repoPath = repoPath;\n this._maxFileSize = maxFileSize;\n }\n\n /**\n * Index all supported files in the repository.\n * Skips unchanged files (same content hash).\n */\n async index(options: CodeIndexOptions = {}): Promise<IndexResult> {\n const { forceReindex = false, onProgress } = options;\n const files = this._walkRepo(this._repoPath);\n let indexed = 0, skipped = 0, totalChunks = 0;\n\n for (let i = 0; i < files.length; i++) {\n const filePath = files[i];\n const rel = path.relative(this._repoPath, filePath);\n onProgress?.(rel, i + 1, files.length);\n\n let content: string;\n try { content = fs.readFileSync(filePath, 'utf-8'); }\n catch { continue; }\n\n const hash = this._hash(content);\n const existing = this._deps.db.prepare(\n 'SELECT file_hash FROM indexed_files WHERE file_path = ?'\n ).get(rel) as any;\n\n if (!forceReindex && existing?.file_hash === hash) {\n skipped++;\n continue;\n }\n\n // Always clean up old chunks for this file (catches orphaned chunks from interrupted first-run)\n const oldChunks = this._deps.db.prepare(\n 'SELECT id FROM code_chunks WHERE file_path = ?'\n ).all(rel) as any[];\n if (oldChunks.length > 0) {\n for (const { id } of oldChunks) {\n this._deps.hnsw.remove(id);\n this._deps.vectorCache.delete(id);\n }\n this._deps.db.prepare('DELETE FROM code_chunks WHERE file_path = ?').run(rel);\n }\n\n const ext = path.extname(filePath).toLowerCase();\n const language = SUPPORTED_EXTENSIONS[ext] ?? 'text';\n const chunks = await this._chunker.chunk(rel, content, language);\n\n // Build embedding texts for all chunks and batch embed (10-50x faster)\n const embeddingTexts = chunks.map(chunk => [\n `File: ${rel}`,\n chunk.name ? `${chunk.chunkType}: ${chunk.name}` : chunk.chunkType,\n chunk.content,\n ].join('\\n'));\n\n const vecs = await this._deps.embedding.embedBatch(embeddingTexts);\n\n // Insert chunks + vectors individually (preserves auto-increment ID ordering)\n for (let ci = 0; ci < chunks.length; ci++) {\n const chunk = chunks[ci];\n const result = this._deps.db.prepare(\n `INSERT INTO code_chunks (file_path, chunk_type, name, start_line, end_line, content, language, file_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n ).run(rel, chunk.chunkType, chunk.name ?? null, chunk.startLine, chunk.endLine, chunk.content, language, hash);\n\n const id = Number(result.lastInsertRowid);\n this._deps.db.prepare(\n 'INSERT INTO code_vectors (chunk_id, embedding) VALUES (?, ?)'\n ).run(id, Buffer.from(vecs[ci].buffer));\n\n this._deps.hnsw.add(vecs[ci], id);\n this._deps.vectorCache.set(id, vecs[ci]);\n totalChunks++;\n }\n\n // Mark file as indexed\n this._deps.db.prepare(\n 'INSERT OR REPLACE INTO indexed_files (file_path, file_hash) VALUES (?, ?)'\n ).run(rel, hash);\n indexed++;\n }\n\n return { indexed, skipped, chunks: totalChunks };\n }\n\n // ── File Walker ─────────────────────────────────\n\n private _walkRepo(dir: string, files: string[] = []): string[] {\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); }\n catch { return files; }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (isIgnoredDir(entry.name)) continue;\n this._walkRepo(path.join(dir, entry.name), files);\n } else if (entry.isFile()) {\n if (isIgnoredFile(entry.name)) continue;\n const ext = path.extname(entry.name).toLowerCase();\n if (!(ext in SUPPORTED_EXTENSIONS)) continue;\n\n const full = path.join(dir, entry.name);\n try {\n if (fs.statSync(full).size <= this._maxFileSize) {\n files.push(full);\n }\n } catch {}\n }\n }\n return files;\n }\n\n // ── FNV-1a Hash ─────────────────────────────────\n\n private _hash(content: string): string {\n let h = 2166136261;\n for (let i = 0; i < content.length; i++) {\n h ^= content.charCodeAt(i);\n h = (h * 16777619) >>> 0;\n }\n return h.toString(16);\n }\n}\n","/**\n * BrainBank — Tree-Sitter Code Chunker\n * \n * AST-aware code splitting using native tree-sitter bindings.\n * Extracts semantic blocks (functions, classes, methods, interfaces)\n * from the AST. Falls back to sliding window for unsupported languages.\n */\n\nimport { createRequire } from 'node:module';\nimport type { CodeChunk } from '../../types.ts';\nimport { GRAMMARS, type LangGrammar } from './grammars.ts';\n\nconst require = createRequire(import.meta.url);\n\n// ── Configuration ───────────────────────────────────\n\nexport interface ChunkerConfig {\n /** Max lines per chunk. Default: 80 */\n maxLines?: number;\n /** Min lines for a detected block to be a chunk. Default: 3 */\n minLines?: number;\n /** Overlap between adjacent generic chunks. Default: 5 */\n overlap?: number;\n}\n\n// ── CodeChunker ─────────────────────────────────────\n\nexport class CodeChunker {\n private MAX: number;\n private MIN: number;\n private OVERLAP: number;\n private _parser: any = null;\n private _langCache = new Map<string, LangGrammar | null>();\n\n constructor(config: ChunkerConfig = {}) {\n this.MAX = config.maxLines ?? 80;\n this.MIN = config.minLines ?? 3;\n this.OVERLAP = config.overlap ?? 5;\n }\n\n /** Lazy-init tree-sitter parser. */\n private _ensureParser(): any {\n if (!this._parser) {\n try {\n const Parser = require('tree-sitter');\n this._parser = new Parser();\n } catch {\n this._parser = false; // Mark as unavailable\n }\n }\n return this._parser || null;\n }\n\n /** Load a language grammar (cached). */\n private _loadGrammar(language: string): LangGrammar | null {\n if (this._langCache.has(language)) return this._langCache.get(language)!;\n\n const factory = GRAMMARS[language];\n const grammar = factory ? factory() : null;\n this._langCache.set(language, grammar);\n return grammar;\n }\n\n /**\n * Split file content into semantic chunks using tree-sitter AST.\n * Falls back to sliding window if grammar isn't available.\n */\n async chunk(filePath: string, content: string, language: string): Promise<CodeChunk[]> {\n const lines = content.split('\\n');\n\n // Small file → single chunk\n if (lines.length <= this.MAX) {\n return [{\n filePath,\n chunkType: 'file',\n startLine: 1,\n endLine: lines.length,\n content: content.trim(),\n language,\n }];\n }\n\n // Try tree-sitter AST chunking\n const parser = this._ensureParser();\n const langConfig = this._loadGrammar(language);\n\n if (parser && langConfig) {\n try {\n parser.setLanguage(langConfig.grammar);\n const tree = parser.parse(content);\n const chunks = this._extractChunks(filePath, lines, tree.rootNode, langConfig, language);\n\n if (chunks.length > 0) {\n return chunks.filter(c => c.content.length > 20);\n }\n } catch {\n // Tree-sitter failed — fall through to generic\n }\n }\n\n // Fallback to sliding window\n return this._chunkGeneric(filePath, lines, language);\n }\n\n /** Walk AST and extract top-level semantic blocks. */\n private _extractChunks(\n filePath: string, lines: string[],\n rootNode: any, langConfig: LangGrammar, language: string,\n ): CodeChunk[] {\n const chunks: CodeChunk[] = [];\n const seen = new Set<string>();\n\n for (let i = 0; i < rootNode.childCount; i++) {\n const child = rootNode.child(i);\n this._processNode(filePath, lines, child, langConfig, language, chunks, seen);\n }\n\n return chunks;\n }\n\n /** Classify and process a single AST node. */\n private _processNode(\n filePath: string, lines: string[], node: any,\n langConfig: LangGrammar, language: string,\n chunks: CodeChunk[], seen: Set<string>,\n ): void {\n const type = node.type;\n\n // Handle export_statement: process what it wraps\n if (type === 'export_statement') {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n const category = this._categorize(child.type, langConfig);\n if (category) {\n this._processDeclaration(filePath, lines, node, child, category, langConfig, language, chunks, seen);\n return;\n }\n }\n // Export with no recognized declaration — chunk the whole thing if big enough\n const nodeLines = node.endPosition.row - node.startPosition.row + 1;\n if (nodeLines >= this.MIN) {\n this._addChunk(filePath, lines, node, 'function', this._extractName(node), language, chunks, seen);\n }\n return;\n }\n\n // Python decorated definitions (@decorator + class/def)\n if (type === 'decorated_definition') {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n const category = this._categorize(child.type, langConfig);\n if (category) {\n this._processDeclaration(filePath, lines, node, child, category, langConfig, language, chunks, seen);\n return;\n }\n }\n }\n\n // Direct match\n const category = this._categorize(type, langConfig);\n if (category) {\n this._processDeclaration(filePath, lines, node, node, category, langConfig, language, chunks, seen);\n }\n }\n\n /** Check which category a node type belongs to. */\n private _categorize(nodeType: string, langConfig: LangGrammar): string | null {\n for (const [category, types] of Object.entries(langConfig.nodeTypes)) {\n if (types && types.includes(nodeType)) return category;\n }\n return null;\n }\n\n /** Process a matched declaration: class → split by methods, else → chunk directly. */\n private _processDeclaration(\n filePath: string, lines: string[],\n outerNode: any, innerNode: any, category: string,\n langConfig: LangGrammar, language: string,\n chunks: CodeChunk[], seen: Set<string>,\n ): void {\n const nodeLines = outerNode.endPosition.row - outerNode.startPosition.row + 1;\n const name = this._extractName(innerNode);\n const chunkType = this._toChunkType(category);\n\n // Large class → split into methods\n if ((category === 'class' || category === 'struct' || category === 'impl') && nodeLines > this.MAX) {\n this._splitClassIntoMethods(filePath, lines, outerNode, innerNode, name, langConfig, language, chunks, seen);\n return;\n }\n\n // Large non-class → split with overlap\n if (nodeLines > this.MAX) {\n chunks.push(...this._splitLargeBlock(filePath, lines,\n outerNode.startPosition.row, outerNode.endPosition.row,\n name, chunkType, language));\n return;\n }\n\n // Normal-sized node\n if (nodeLines >= this.MIN) {\n this._addChunk(filePath, lines, outerNode, chunkType, name, language, chunks, seen);\n }\n }\n\n /** Split a large class into individual method chunks. */\n private _splitClassIntoMethods(\n filePath: string, lines: string[],\n outerNode: any, classNode: any, className: string,\n langConfig: LangGrammar, language: string,\n chunks: CodeChunk[], seen: Set<string>,\n ): void {\n // Find class body\n const body = this._findClassBody(classNode);\n if (!body) {\n chunks.push(...this._splitLargeBlock(filePath, lines,\n outerNode.startPosition.row, outerNode.endPosition.row,\n className, 'class', language));\n return;\n }\n\n // Get method node types\n const methodTypes = new Set([\n ...(langConfig.nodeTypes.function || []),\n ...(langConfig.nodeTypes.method || []),\n ]);\n\n let methodsFound = false;\n for (let i = 0; i < body.childCount; i++) {\n const child = body.child(i);\n let methodNode = child;\n\n // Decorated methods\n if (child.type === 'decorated_definition') {\n for (let j = 0; j < child.childCount; j++) {\n if (methodTypes.has(child.child(j).type)) {\n methodNode = child.child(j);\n break;\n }\n }\n }\n\n if (methodTypes.has(methodNode.type) || methodTypes.has(child.type)) {\n const methodName = this._extractName(methodNode);\n const nodeToChunk = child.type === 'decorated_definition' ? child : methodNode;\n const methodLineCount = nodeToChunk.endPosition.row - nodeToChunk.startPosition.row + 1;\n\n if (methodLineCount >= this.MIN) {\n methodsFound = true;\n const fullName = `${className}.${methodName}`;\n\n if (methodLineCount > this.MAX) {\n chunks.push(...this._splitLargeBlock(filePath, lines,\n nodeToChunk.startPosition.row, nodeToChunk.endPosition.row,\n fullName, 'method', language));\n } else {\n this._addChunk(filePath, lines, nodeToChunk, 'method', fullName, language, chunks, seen);\n }\n }\n }\n }\n\n // Fallback: no methods found → split the whole class\n if (!methodsFound) {\n chunks.push(...this._splitLargeBlock(filePath, lines,\n outerNode.startPosition.row, outerNode.endPosition.row,\n className, 'class', language));\n }\n }\n\n /** Find the class body node. */\n private _findClassBody(classNode: any): any | null {\n const bodyTypes = ['class_body', 'block', 'declaration_list', 'body'];\n for (let i = 0; i < classNode.childCount; i++) {\n const child = classNode.child(i);\n if (bodyTypes.includes(child.type)) return child;\n }\n return null;\n }\n\n /** Extract name from an AST node. */\n private _extractName(node: any): string {\n // Try childForFieldName('name')\n if (typeof node.childForFieldName === 'function') {\n const nameNode = node.childForFieldName('name');\n if (nameNode) return nameNode.text;\n }\n // Try identifier children\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (['identifier', 'type_identifier', 'property_identifier'].includes(child.type)) {\n return child.text;\n }\n }\n // For variable declarations, dig into declarators\n if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (child.type === 'variable_declarator') {\n const nameNode = child.childForFieldName('name');\n if (nameNode) return nameNode.text;\n }\n }\n }\n return 'anonymous';\n }\n\n /** Map category to chunk type. */\n private _toChunkType(category: string): string {\n if (category === 'class' || category === 'struct' || category === 'impl') return 'class';\n if (category === 'interface') return 'interface';\n if (category === 'variable') return 'function';\n return category;\n }\n\n /** Add a node as a chunk, avoiding duplicates. */\n private _addChunk(\n filePath: string, lines: string[], node: any,\n chunkType: string, name: string, language: string,\n chunks: CodeChunk[], seen: Set<string>,\n ): void {\n const start = node.startPosition.row;\n const end = node.endPosition.row;\n const key = `${start}-${end}`;\n if (seen.has(key)) return;\n seen.add(key);\n\n const content = lines.slice(start, end + 1).join('\\n').trim();\n if (content.length <= 20) return;\n\n chunks.push({\n filePath,\n chunkType,\n name,\n startLine: start + 1,\n endLine: end + 1,\n content,\n language,\n });\n }\n\n // ── Fallback: Generic sliding window ────────────\n\n private _chunkGeneric(filePath: string, lines: string[], language: string): CodeChunk[] {\n const chunks: CodeChunk[] = [];\n const step = Math.max(this.MAX - this.OVERLAP, 1);\n\n for (let s = 0; s < lines.length; s += step) {\n const e = Math.min(s + this.MAX, lines.length);\n const content = lines.slice(s, e).join('\\n').trim();\n if (content.length > 20) {\n chunks.push({\n filePath,\n chunkType: 'block',\n startLine: s + 1,\n endLine: e,\n content,\n language,\n });\n }\n if (e >= lines.length) break;\n }\n\n return chunks;\n }\n\n /** Split a large block into overlapping sub-chunks. */\n private _splitLargeBlock(\n filePath: string, lines: string[],\n start: number, end: number,\n name: string, type: string, language: string,\n ): CodeChunk[] {\n const chunks: CodeChunk[] = [];\n const step = Math.max(this.MAX - this.OVERLAP, 1);\n let part = 1;\n\n for (let s = start; s <= end; s += step) {\n const e = Math.min(s + this.MAX, end + 1);\n const content = lines.slice(s, e).join('\\n').trim();\n if (content.length > 20) {\n chunks.push({\n filePath,\n chunkType: type,\n name: `${name} (part ${part++})`,\n startLine: s + 1,\n endLine: e,\n content,\n language,\n });\n }\n if (e > end) break;\n }\n\n return chunks;\n }\n}\n","/**\n * BrainBank — Tree-Sitter Grammar Registry\n *\n * Maps language names to their tree-sitter grammar packages\n * and the AST node types that represent semantic blocks.\n */\n\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\n\n// ── Types ───────────────────────────────────────────\n\nexport interface LangGrammar {\n grammar: any;\n nodeTypes: {\n class?: string[];\n function?: string[];\n interface?: string[];\n variable?: string[];\n method?: string[];\n struct?: string[];\n impl?: string[];\n };\n}\n\n// ── Loader ──────────────────────────────────────────\n\n/** Try to require a grammar, return null if not installed. */\nfunction tryGrammar(pkg: string, nodeTypes: LangGrammar['nodeTypes'], accessor?: string): () => LangGrammar | null {\n return () => {\n try {\n const mod = require(pkg);\n return { grammar: accessor ? mod[accessor] : mod, nodeTypes };\n } catch { return null; }\n };\n}\n\n// ── Grammar Table ───────────────────────────────────\n\nexport const GRAMMARS: Record<string, () => LangGrammar | null> = {\n // ── Web ──────────────────────────────────────────\n typescript: tryGrammar('tree-sitter-typescript', {\n class: ['class_declaration'],\n interface: ['interface_declaration', 'type_alias_declaration'],\n function: ['function_declaration', 'method_definition'],\n variable: ['lexical_declaration'],\n }, 'typescript'),\n javascript: tryGrammar('tree-sitter-javascript', {\n class: ['class_declaration'],\n function: ['function_declaration', 'method_definition'],\n variable: ['lexical_declaration'],\n }),\n html: tryGrammar('tree-sitter-html', {}),\n css: tryGrammar('tree-sitter-css', {}),\n\n // ── Systems ──────────────────────────────────────\n go: tryGrammar('tree-sitter-go', {\n function: ['function_declaration', 'method_declaration'],\n struct: ['type_declaration'],\n }),\n rust: tryGrammar('tree-sitter-rust', {\n function: ['function_item'],\n struct: ['struct_item'],\n impl: ['impl_item'],\n }),\n c: tryGrammar('tree-sitter-c', {\n function: ['function_definition'],\n struct: ['struct_specifier'],\n }),\n cpp: tryGrammar('tree-sitter-cpp', {\n class: ['class_specifier'],\n function: ['function_definition'],\n }),\n swift: tryGrammar('tree-sitter-swift', {\n class: ['class_declaration'],\n function: ['function_declaration'],\n struct: ['struct_declaration'],\n }),\n\n // ── JVM ──────────────────────────────────────────\n java: tryGrammar('tree-sitter-java', {\n class: ['class_declaration'],\n interface: ['interface_declaration'],\n method: ['method_declaration'],\n }),\n kotlin: tryGrammar('tree-sitter-kotlin', {\n class: ['class_declaration'],\n function: ['function_declaration'],\n }),\n scala: tryGrammar('tree-sitter-scala', {\n class: ['class_definition'],\n function: ['function_definition'],\n }),\n\n // ── Scripting ────────────────────────────────────\n python: tryGrammar('tree-sitter-python', {\n class: ['class_definition'],\n function: ['function_definition'],\n }),\n ruby: tryGrammar('tree-sitter-ruby', {\n class: ['class'],\n method: ['method', 'singleton_method'],\n }),\n php: tryGrammar('tree-sitter-php', {\n class: ['class_declaration'],\n function: ['function_definition', 'method_declaration'],\n }, 'php'),\n lua: tryGrammar('tree-sitter-lua', {\n function: ['function_declaration'],\n }),\n bash: tryGrammar('tree-sitter-bash', {\n function: ['function_definition'],\n }),\n elixir: tryGrammar('tree-sitter-elixir', {\n function: ['call'], // defmodule, def, defp\n }),\n\n // ── .NET ─────────────────────────────────────────\n c_sharp: tryGrammar('tree-sitter-c-sharp', {\n class: ['class_declaration'],\n interface: ['interface_declaration'],\n method: ['method_declaration'],\n }),\n};\n","/**\n * BrainBank — Language Registry\n * \n * Supported file extensions, language mappings, and ignore lists.\n * Controls which files get indexed and how they're chunked.\n */\n\n// ── Supported Extensions ────────────────────────────\n\nexport const SUPPORTED_EXTENSIONS: Record<string, string> = {\n // TypeScript / JavaScript\n '.ts': 'typescript',\n '.tsx': 'typescript',\n '.js': 'javascript',\n '.jsx': 'javascript',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n\n // Systems\n '.go': 'go',\n '.rs': 'rust',\n '.cpp': 'cpp',\n '.cc': 'cpp',\n '.c': 'c',\n '.h': 'c',\n '.hpp': 'cpp',\n\n // JVM\n '.java': 'java',\n '.kt': 'kotlin',\n '.scala': 'scala',\n\n // Scripting\n '.py': 'python',\n '.rb': 'ruby',\n '.php': 'php',\n '.lua': 'lua',\n '.sh': 'bash',\n '.bash': 'bash',\n '.zsh': 'bash',\n\n // Web\n '.html': 'html',\n '.css': 'css',\n '.scss': 'scss',\n '.less': 'less',\n '.svelte': 'svelte',\n '.vue': 'vue',\n\n // Data / Config\n '.json': 'json',\n '.yaml': 'yaml',\n '.yml': 'yaml',\n '.toml': 'toml',\n '.xml': 'xml',\n '.graphql': 'graphql',\n '.gql': 'graphql',\n\n // Docs\n '.md': 'markdown',\n '.mdx': 'markdown',\n\n // Database\n '.sql': 'sql',\n '.prisma': 'prisma',\n\n // Other\n '.swift': 'swift',\n '.dart': 'dart',\n '.r': 'r',\n '.ex': 'elixir',\n '.exs': 'elixir',\n '.erl': 'erlang',\n '.zig': 'zig',\n};\n\n// ── Ignore Directories ──────────────────────────────\n\nexport const IGNORE_DIRS = new Set([\n // Package managers\n 'node_modules',\n 'bower_components',\n '.pnpm',\n\n // Build output\n 'dist',\n 'build',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '.svelte-kit',\n\n // Version control\n '.git',\n '.hg',\n '.svn',\n\n // IDE / Editor\n '.idea',\n '.vscode',\n\n // Runtime / Cache\n '__pycache__',\n '.pytest_cache',\n 'venv',\n '.venv',\n '.env',\n '.tox',\n\n // Coverage / Test artifacts\n 'coverage',\n '.nyc_output',\n 'htmlcov',\n\n // Compiled\n 'target', // Rust, Java\n '.cargo',\n 'vendor', // Go, PHP\n\n // AI / Model cache\n '.model-cache',\n '.brainbank',\n\n // OS\n '.DS_Store',\n]);\n\n// ── Ignore Files ────────────────────────────────────\n\nexport const IGNORE_FILES = new Set([\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n 'bun.lockb',\n 'Cargo.lock',\n 'Gemfile.lock',\n 'poetry.lock',\n 'composer.lock',\n 'go.sum',\n]);\n\n// ── Helpers ─────────────────────────────────────────\n\nimport path from 'node:path';\n\n/** Check if a file extension is supported for indexing. */\nexport function isSupported(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase();\n return ext in SUPPORTED_EXTENSIONS;\n}\n\n/** Get the language name for a file. Returns undefined if not supported. */\nexport function getLanguage(filePath: string): string | undefined {\n const ext = path.extname(filePath).toLowerCase();\n return SUPPORTED_EXTENSIONS[ext];\n}\n\n/** Check if a directory name should be ignored. */\nexport function isIgnoredDir(dirName: string): boolean {\n return IGNORE_DIRS.has(dirName);\n}\n\n/** Check if a filename should be ignored. */\nexport function isIgnoredFile(fileName: string): boolean {\n return IGNORE_FILES.has(fileName);\n}\n","/**\n * BrainBank — Code Module\n * \n * Language-aware code indexing for 30+ languages.\n * \n * import { BrainBank } from 'brainbank';\n * import { code } from 'brainbank/code';\n * \n * const brain = new BrainBank().use(code({ repoPath: '.' }));\n * \n * // Multi-repo: namespace to avoid key collisions\n * brain\n * .use(code({ repoPath: './frontend', name: 'code:frontend' }))\n * .use(code({ repoPath: './backend', name: 'code:backend' }));\n */\n\nimport type { Indexer, IndexerContext } from '../base.ts';\nimport type { HNSWIndex } from '../../providers/vector/hnsw.ts';\nimport { CodeIndexer } from './code-indexer.ts';\nimport type { IndexResult, ProgressCallback } from '../../types.ts';\n\nexport interface CodePluginOptions {\n /** Repository path to index. Default: '.' */\n repoPath?: string;\n /** Maximum file size in bytes. Default: from config */\n maxFileSize?: number;\n /** Custom indexer name for multi-repo (e.g. 'code:frontend'). Default: 'code' */\n name?: string;\n}\n\nclass CodePlugin implements Indexer {\n readonly name: string;\n hnsw!: HNSWIndex;\n indexer!: CodeIndexer;\n vecCache = new Map<number, Float32Array>();\n\n constructor(private opts: CodePluginOptions = {}) {\n this.name = opts.name ?? 'code';\n }\n\n async initialize(ctx: IndexerContext): Promise<void> {\n // Use shared HNSW so all code indexers (code, code:frontend, etc.) share one index\n const shared = await ctx.getOrCreateSharedHnsw('code');\n this.hnsw = shared.hnsw;\n this.vecCache = shared.vecCache;\n\n // Only load vectors once (first code indexer to initialize)\n if (shared.isNew) {\n ctx.loadVectors('code_vectors', 'chunk_id', this.hnsw, this.vecCache);\n }\n\n const repoPath = this.opts.repoPath ?? ctx.config.repoPath;\n this.indexer = new CodeIndexer(repoPath, {\n db: ctx.db,\n hnsw: this.hnsw,\n vectorCache: this.vecCache,\n embedding: ctx.embedding,\n }, this.opts.maxFileSize ?? ctx.config.maxFileSize);\n }\n\n async index(options: {\n forceReindex?: boolean;\n onProgress?: ProgressCallback;\n } = {}): Promise<IndexResult> {\n return this.indexer.index(options);\n }\n\n stats(): Record<string, any> {\n return { hnswSize: this.hnsw.size };\n }\n}\n\n/** Create a code indexing plugin. */\nexport function code(opts?: CodePluginOptions): Indexer {\n return new CodePlugin(opts);\n}\n"],"mappings":"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","names":["path","createRequire","require","require","createRequire","category","path"]}
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  cosineSimilarity
3
- } from "./chunk-2P3EGY6S.js";
3
+ } from "./chunk-QNHBCOKB.js";
4
4
  import {
5
5
  __name
6
6
  } from "./chunk-7QVYU63E.js";
7
7
 
8
- // src/memory/pattern-store.ts
8
+ // src/memory/store.ts
9
9
  var PatternStore = class {
10
10
  static {
11
11
  __name(this, "PatternStore");
@@ -168,4 +168,4 @@ export {
168
168
  PatternStore,
169
169
  Consolidator
170
170
  };
171
- //# sourceMappingURL=chunk-Z5SU54HP.js.map
171
+ //# sourceMappingURL=chunk-X6645UVR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/memory/store.ts","../src/memory/consolidator.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 type { EmbeddingProvider, LearningPattern } from '../types.ts';\nimport type { HNSWIndex } from '../providers/vector/hnsw.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, Buffer.from(vec.buffer));\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"],"mappings":"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","names":[]}