compound-agent 1.5.0 → 1.6.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/dist/index.js CHANGED
@@ -1,19 +1,245 @@
1
- import { createRequire } from 'module';
2
- import { mkdirSync, unlinkSync, existsSync, statSync, readFileSync, readdirSync } from 'fs';
3
- import { join, dirname, extname, relative } from 'path';
4
1
  import { createHash } from 'crypto';
5
- import { readFile, mkdir, appendFile, readdir } from 'fs/promises';
6
2
  import { z } from 'zod';
7
- import { getLlama, LlamaLogLevel, resolveModelFile } from 'node-llama-cpp';
3
+ import { mkdir, appendFile, readFile, readdir } from 'fs/promises';
4
+ import { join, dirname, extname, relative } from 'path';
5
+ import { createRequire } from 'module';
6
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync, readFileSync, statSync, readdirSync } from 'fs';
8
7
  import { homedir } from 'os';
9
- import { execSync } from 'child_process';
10
- import 'url';
8
+ import { resolveModelFile, getLlama, LlamaLogLevel } from 'node-llama-cpp';
9
+ import { spawn, execSync } from 'child_process';
10
+ import { fileURLToPath } from 'url';
11
11
  import 'chalk';
12
+ import 'readline';
12
13
 
14
+ var __defProp = Object.defineProperty;
13
15
  var __getOwnPropNames = Object.getOwnPropertyNames;
14
16
  var __esm = (fn, res) => function __init() {
15
17
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
18
  };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, { get: all[name], enumerable: true });
22
+ };
23
+ function generateId(insight, type) {
24
+ const prefix = TYPE_PREFIXES[type ?? "lesson"];
25
+ const hash = createHash("sha256").update(insight).digest("hex");
26
+ return `${prefix}${hash.slice(0, 8)}`;
27
+ }
28
+ var SourceSchema, ContextSchema, PatternSchema, CitationSchema, SeveritySchema, CompactionLevelSchema, LessonTypeSchema, MemoryItemTypeSchema, baseFields, LessonItemSchema, SolutionItemSchema, PatternItemSchema, PreferenceItemSchema, MemoryItemSchema, LegacyLessonSchema, LessonSchema, LegacyTombstoneSchema, LessonRecordSchema, MemoryItemRecordSchema, TYPE_PREFIXES;
29
+ var init_types = __esm({
30
+ "src/memory/types.ts"() {
31
+ SourceSchema = z.enum([
32
+ "user_correction",
33
+ "self_correction",
34
+ "test_failure",
35
+ "manual"
36
+ ]);
37
+ ContextSchema = z.object({
38
+ tool: z.string(),
39
+ intent: z.string()
40
+ });
41
+ PatternSchema = z.object({
42
+ bad: z.string(),
43
+ good: z.string()
44
+ });
45
+ CitationSchema = z.object({
46
+ file: z.string().min(1),
47
+ // Source file path (required, non-empty)
48
+ line: z.number().int().positive().optional(),
49
+ // Line number (optional, must be positive)
50
+ commit: z.string().optional()
51
+ // Git commit hash (optional)
52
+ });
53
+ SeveritySchema = z.enum(["high", "medium", "low"]);
54
+ CompactionLevelSchema = z.union([
55
+ z.literal(0),
56
+ // Active
57
+ z.literal(1),
58
+ // Flagged (>90 days)
59
+ z.literal(2)
60
+ // Archived
61
+ ]);
62
+ LessonTypeSchema = z.enum(["quick", "full"]);
63
+ MemoryItemTypeSchema = z.enum(["lesson", "solution", "pattern", "preference"]);
64
+ baseFields = {
65
+ // Core identity (required)
66
+ id: z.string(),
67
+ trigger: z.string(),
68
+ insight: z.string(),
69
+ // Metadata (required)
70
+ tags: z.array(z.string()),
71
+ source: SourceSchema,
72
+ context: ContextSchema,
73
+ created: z.string(),
74
+ // ISO8601
75
+ confirmed: z.boolean(),
76
+ // Relationships (required, can be empty arrays)
77
+ supersedes: z.array(z.string()),
78
+ related: z.array(z.string()),
79
+ // Extended fields (optional)
80
+ evidence: z.string().optional(),
81
+ severity: SeveritySchema.optional(),
82
+ // Lifecycle fields (optional)
83
+ deleted: z.boolean().optional(),
84
+ deletedAt: z.string().optional(),
85
+ retrievalCount: z.number().optional(),
86
+ // Provenance tracking (optional)
87
+ citation: CitationSchema.optional(),
88
+ // Age-based validity fields (optional)
89
+ compactionLevel: CompactionLevelSchema.optional(),
90
+ compactedAt: z.string().optional(),
91
+ lastRetrieved: z.string().optional(),
92
+ // Invalidation fields (optional)
93
+ invalidatedAt: z.string().optional(),
94
+ invalidationReason: z.string().optional()
95
+ };
96
+ LessonItemSchema = z.object({
97
+ ...baseFields,
98
+ type: z.literal("lesson"),
99
+ pattern: PatternSchema.optional()
100
+ });
101
+ SolutionItemSchema = z.object({
102
+ ...baseFields,
103
+ type: z.literal("solution"),
104
+ pattern: PatternSchema.optional()
105
+ });
106
+ PatternItemSchema = z.object({
107
+ ...baseFields,
108
+ type: z.literal("pattern"),
109
+ pattern: PatternSchema
110
+ });
111
+ PreferenceItemSchema = z.object({
112
+ ...baseFields,
113
+ type: z.literal("preference"),
114
+ pattern: PatternSchema.optional()
115
+ });
116
+ MemoryItemSchema = z.discriminatedUnion("type", [
117
+ LessonItemSchema,
118
+ SolutionItemSchema,
119
+ PatternItemSchema,
120
+ PreferenceItemSchema
121
+ ]);
122
+ LegacyLessonSchema = z.object({
123
+ ...baseFields,
124
+ type: LessonTypeSchema,
125
+ pattern: PatternSchema.optional()
126
+ });
127
+ LessonSchema = LessonItemSchema;
128
+ LegacyTombstoneSchema = z.object({
129
+ id: z.string(),
130
+ deleted: z.literal(true),
131
+ deletedAt: z.string()
132
+ // ISO8601
133
+ });
134
+ LessonRecordSchema = z.union([
135
+ MemoryItemSchema,
136
+ LegacyLessonSchema,
137
+ LegacyTombstoneSchema
138
+ ]);
139
+ MemoryItemRecordSchema = LessonRecordSchema;
140
+ TYPE_PREFIXES = {
141
+ lesson: "L",
142
+ solution: "S",
143
+ pattern: "P",
144
+ preference: "R"
145
+ };
146
+ }
147
+ });
148
+ async function appendMemoryItem(repoRoot, item) {
149
+ const filePath = join(repoRoot, LESSONS_PATH);
150
+ await mkdir(dirname(filePath), { recursive: true });
151
+ const line = JSON.stringify(item) + "\n";
152
+ await appendFile(filePath, line, "utf-8");
153
+ }
154
+ async function appendLesson(repoRoot, lesson) {
155
+ return appendMemoryItem(repoRoot, lesson);
156
+ }
157
+ function parseJsonLine(line, lineNumber, strict, onParseError) {
158
+ let parsed;
159
+ try {
160
+ parsed = JSON.parse(line);
161
+ } catch (err) {
162
+ const parseError = {
163
+ line: lineNumber,
164
+ message: `Invalid JSON: ${err.message}`,
165
+ cause: err
166
+ };
167
+ if (strict) {
168
+ throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
169
+ }
170
+ onParseError?.(parseError);
171
+ return null;
172
+ }
173
+ const result = MemoryItemRecordSchema.safeParse(parsed);
174
+ if (!result.success) {
175
+ const parseError = {
176
+ line: lineNumber,
177
+ message: `Schema validation failed: ${result.error.message}`,
178
+ cause: result.error
179
+ };
180
+ if (strict) {
181
+ throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
182
+ }
183
+ onParseError?.(parseError);
184
+ return null;
185
+ }
186
+ return result.data;
187
+ }
188
+ function toMemoryItem(record) {
189
+ if (record.deleted === true) {
190
+ return null;
191
+ }
192
+ if (record.type === "quick" || record.type === "full") {
193
+ return { ...record, type: "lesson" };
194
+ }
195
+ return record;
196
+ }
197
+ async function readMemoryItems(repoRoot, options = {}) {
198
+ const { strict = false, onParseError } = options;
199
+ const filePath = join(repoRoot, LESSONS_PATH);
200
+ let content;
201
+ try {
202
+ content = await readFile(filePath, "utf-8");
203
+ } catch (err) {
204
+ if (err.code === "ENOENT") {
205
+ return { items: [], skippedCount: 0 };
206
+ }
207
+ throw err;
208
+ }
209
+ const items = /* @__PURE__ */ new Map();
210
+ let skippedCount = 0;
211
+ const lines = content.split("\n");
212
+ for (let i = 0; i < lines.length; i++) {
213
+ const trimmed = lines[i].trim();
214
+ if (!trimmed) continue;
215
+ const record = parseJsonLine(trimmed, i + 1, strict, onParseError);
216
+ if (!record) {
217
+ skippedCount++;
218
+ continue;
219
+ }
220
+ if (record.deleted === true) {
221
+ items.delete(record.id);
222
+ } else {
223
+ const item = toMemoryItem(record);
224
+ if (item) {
225
+ items.set(record.id, item);
226
+ }
227
+ }
228
+ }
229
+ return { items: Array.from(items.values()), skippedCount };
230
+ }
231
+ async function readLessons(repoRoot, options = {}) {
232
+ const result = await readMemoryItems(repoRoot, options);
233
+ const lessons = result.items.filter((item) => item.type === "lesson");
234
+ return { lessons, skippedCount: result.skippedCount };
235
+ }
236
+ var LESSONS_PATH;
237
+ var init_jsonl = __esm({
238
+ "src/memory/storage/jsonl.ts"() {
239
+ init_types();
240
+ LESSONS_PATH = ".claude/lessons/index.jsonl";
241
+ }
242
+ });
17
243
  function ensureSqliteAvailable() {
18
244
  if (checked) return;
19
245
  try {
@@ -43,51 +269,74 @@ var init_availability = __esm({
43
269
  }
44
270
  });
45
271
 
46
- // src/memory/storage/sqlite-knowledge/schema.ts
47
- function createKnowledgeSchema(database) {
48
- database.exec(SCHEMA_SQL2);
49
- database.pragma(`user_version = ${KNOWLEDGE_SCHEMA_VERSION}`);
272
+ // src/memory/storage/sqlite/schema.ts
273
+ function createSchema(database) {
274
+ database.exec(SCHEMA_SQL);
275
+ database.pragma(`user_version = ${SCHEMA_VERSION}`);
50
276
  }
51
- var KNOWLEDGE_SCHEMA_VERSION, SCHEMA_SQL2;
277
+ var SCHEMA_VERSION, SCHEMA_SQL;
52
278
  var init_schema = __esm({
53
- "src/memory/storage/sqlite-knowledge/schema.ts"() {
54
- KNOWLEDGE_SCHEMA_VERSION = 2;
55
- SCHEMA_SQL2 = `
56
- CREATE TABLE IF NOT EXISTS chunks (
279
+ "src/memory/storage/sqlite/schema.ts"() {
280
+ SCHEMA_VERSION = 4;
281
+ SCHEMA_SQL = `
282
+ CREATE TABLE IF NOT EXISTS lessons (
57
283
  id TEXT PRIMARY KEY,
58
- file_path TEXT NOT NULL,
59
- start_line INTEGER NOT NULL,
60
- end_line INTEGER NOT NULL,
61
- content_hash TEXT NOT NULL,
62
- text TEXT NOT NULL,
284
+ type TEXT NOT NULL,
285
+ trigger TEXT NOT NULL,
286
+ insight TEXT NOT NULL,
287
+ evidence TEXT,
288
+ severity TEXT,
289
+ tags TEXT NOT NULL DEFAULT '',
290
+ source TEXT NOT NULL,
291
+ context TEXT NOT NULL DEFAULT '{}',
292
+ supersedes TEXT NOT NULL DEFAULT '[]',
293
+ related TEXT NOT NULL DEFAULT '[]',
294
+ created TEXT NOT NULL,
295
+ confirmed INTEGER NOT NULL DEFAULT 0,
296
+ deleted INTEGER NOT NULL DEFAULT 0,
297
+ retrieval_count INTEGER NOT NULL DEFAULT 0,
298
+ last_retrieved TEXT,
63
299
  embedding BLOB,
64
- model TEXT,
65
- updated_at TEXT NOT NULL
300
+ content_hash TEXT,
301
+ embedding_insight BLOB,
302
+ content_hash_insight TEXT,
303
+ invalidated_at TEXT,
304
+ invalidation_reason TEXT,
305
+ citation_file TEXT,
306
+ citation_line INTEGER,
307
+ citation_commit TEXT,
308
+ compaction_level INTEGER DEFAULT 0,
309
+ compacted_at TEXT,
310
+ pattern_bad TEXT,
311
+ pattern_good TEXT
66
312
  );
67
313
 
68
- CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
69
- text,
70
- content='chunks', content_rowid='rowid'
314
+ CREATE VIRTUAL TABLE IF NOT EXISTS lessons_fts USING fts5(
315
+ id, trigger, insight, tags, pattern_bad, pattern_good,
316
+ content='lessons', content_rowid='rowid'
71
317
  );
72
318
 
73
- CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
74
- INSERT INTO chunks_fts(rowid, text)
75
- VALUES (new.rowid, new.text);
319
+ CREATE TRIGGER IF NOT EXISTS lessons_ai AFTER INSERT ON lessons BEGIN
320
+ INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
321
+ VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
76
322
  END;
77
323
 
78
- CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
79
- INSERT INTO chunks_fts(chunks_fts, rowid, text)
80
- VALUES ('delete', old.rowid, old.text);
324
+ CREATE TRIGGER IF NOT EXISTS lessons_ad AFTER DELETE ON lessons BEGIN
325
+ INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
326
+ VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
81
327
  END;
82
328
 
83
- CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
84
- INSERT INTO chunks_fts(chunks_fts, rowid, text)
85
- VALUES ('delete', old.rowid, old.text);
86
- INSERT INTO chunks_fts(rowid, text)
87
- VALUES (new.rowid, new.text);
329
+ CREATE TRIGGER IF NOT EXISTS lessons_au AFTER UPDATE ON lessons BEGIN
330
+ INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
331
+ VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
332
+ INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
333
+ VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
88
334
  END;
89
335
 
90
- CREATE INDEX IF NOT EXISTS idx_chunks_file_path ON chunks(file_path);
336
+ CREATE INDEX IF NOT EXISTS idx_lessons_created ON lessons(created);
337
+ CREATE INDEX IF NOT EXISTS idx_lessons_confirmed ON lessons(confirmed);
338
+ CREATE INDEX IF NOT EXISTS idx_lessons_severity ON lessons(severity);
339
+ CREATE INDEX IF NOT EXISTS idx_lessons_type ON lessons(type);
91
340
 
92
341
  CREATE TABLE IF NOT EXISTS metadata (
93
342
  key TEXT PRIMARY KEY,
@@ -96,10 +345,14 @@ var init_schema = __esm({
96
345
  `;
97
346
  }
98
347
  });
99
- function openKnowledgeDb(repoRoot, options = {}) {
348
+ function hasExpectedVersion(database) {
349
+ const row = database.pragma("user_version", { simple: true });
350
+ return row === SCHEMA_VERSION;
351
+ }
352
+ function openDb(repoRoot, options = {}) {
100
353
  const { inMemory = false } = options;
101
- const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, KNOWLEDGE_DB_PATH);
102
- const cached = knowledgeDbMap.get(key);
354
+ const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, DB_PATH);
355
+ const cached = dbMap.get(key);
103
356
  if (cached) {
104
357
  return cached;
105
358
  }
@@ -111,8 +364,7 @@ function openKnowledgeDb(repoRoot, options = {}) {
111
364
  const dir = dirname(key);
112
365
  mkdirSync(dir, { recursive: true });
113
366
  database = new Database(key);
114
- const version = database.pragma("user_version", { simple: true });
115
- if (version !== 0 && version !== KNOWLEDGE_SCHEMA_VERSION) {
367
+ if (!hasExpectedVersion(database)) {
116
368
  database.close();
117
369
  try {
118
370
  unlinkSync(key);
@@ -122,679 +374,1004 @@ function openKnowledgeDb(repoRoot, options = {}) {
122
374
  }
123
375
  database.pragma("journal_mode = WAL");
124
376
  }
125
- createKnowledgeSchema(database);
126
- knowledgeDbMap.set(key, database);
377
+ createSchema(database);
378
+ dbMap.set(key, database);
127
379
  return database;
128
380
  }
129
- function closeKnowledgeDb() {
130
- for (const database of knowledgeDbMap.values()) {
381
+ function closeDb() {
382
+ for (const database of dbMap.values()) {
131
383
  database.close();
132
384
  }
133
- knowledgeDbMap.clear();
385
+ dbMap.clear();
134
386
  }
135
- var KNOWLEDGE_DB_PATH, knowledgeDbMap;
387
+ var DB_PATH, dbMap;
136
388
  var init_connection = __esm({
137
- "src/memory/storage/sqlite-knowledge/connection.ts"() {
389
+ "src/memory/storage/sqlite/connection.ts"() {
138
390
  init_availability();
139
391
  init_schema();
140
- KNOWLEDGE_DB_PATH = ".claude/.cache/knowledge.sqlite";
141
- knowledgeDbMap = /* @__PURE__ */ new Map();
392
+ DB_PATH = ".claude/.cache/lessons.sqlite";
393
+ dbMap = /* @__PURE__ */ new Map();
142
394
  }
143
395
  });
144
- function generateChunkId(filePath, startLine, endLine) {
145
- return createHash("sha256").update(`${filePath}:${startLine}:${endLine}`).digest("hex").slice(0, 16);
396
+ function contentHash(trigger, insight) {
397
+ return createHash("sha256").update(`${trigger} ${insight}`).digest("hex");
146
398
  }
147
- function chunkContentHash(text) {
148
- return createHash("sha256").update(text).digest("hex");
399
+ function setCachedEmbedding(repoRoot, lessonId, embedding, hash) {
400
+ const database = openDb(repoRoot);
401
+ const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
402
+ const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
403
+ database.prepare("UPDATE lessons SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, lessonId);
149
404
  }
150
- var SUPPORTED_EXTENSIONS, CODE_EXTENSIONS;
151
- var init_types = __esm({
152
- "src/memory/knowledge/types.ts"() {
153
- SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
154
- ".md",
155
- ".txt",
156
- ".rst",
157
- ".ts",
158
- ".py",
159
- ".js",
160
- ".tsx",
161
- ".jsx"
162
- ]);
163
- CODE_EXTENSIONS = /* @__PURE__ */ new Set([
164
- ".ts",
165
- ".tsx",
166
- ".js",
167
- ".jsx",
168
- ".py"
169
- ]);
405
+ function getCachedEmbeddingsBulk(repoRoot) {
406
+ const database = openDb(repoRoot);
407
+ const rows = database.prepare("SELECT id, embedding, content_hash FROM lessons WHERE embedding IS NOT NULL").all();
408
+ const result = /* @__PURE__ */ new Map();
409
+ for (const row of rows) {
410
+ if (!row.content_hash) continue;
411
+ const float32 = new Float32Array(
412
+ row.embedding.buffer,
413
+ row.embedding.byteOffset,
414
+ row.embedding.byteLength / 4
415
+ );
416
+ result.set(row.id, { vector: float32, hash: row.content_hash });
170
417
  }
171
- });
172
-
173
- // src/memory/storage/sqlite-knowledge/sync.ts
174
- function upsertChunks(repoRoot, chunks, embeddings) {
175
- if (chunks.length === 0) return;
176
- const database = openKnowledgeDb(repoRoot);
177
- const insert = database.prepare(`
178
- INSERT OR REPLACE INTO chunks (id, file_path, start_line, end_line, content_hash, text, embedding, model, updated_at)
179
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
180
- `);
181
- const upsertMany = database.transaction((items) => {
182
- for (const chunk of items) {
183
- const embBuffer = null;
184
- insert.run(
185
- chunk.id,
186
- chunk.filePath,
187
- chunk.startLine,
188
- chunk.endLine,
189
- chunk.contentHash,
190
- chunk.text,
191
- embBuffer,
192
- chunk.model ?? null,
193
- chunk.updatedAt
194
- );
195
- }
196
- });
197
- upsertMany(chunks);
198
- }
199
- function deleteChunksByFilePath(repoRoot, filePaths) {
200
- if (filePaths.length === 0) return;
201
- const database = openKnowledgeDb(repoRoot);
202
- const del = database.prepare("DELETE FROM chunks WHERE file_path = ?");
203
- const deleteMany = database.transaction((paths) => {
204
- for (const path2 of paths) {
205
- del.run(path2);
206
- }
207
- });
208
- deleteMany(filePaths);
418
+ return result;
209
419
  }
210
- function getIndexedFilePaths(repoRoot) {
211
- const database = openKnowledgeDb(repoRoot);
212
- const rows = database.prepare("SELECT DISTINCT file_path FROM chunks").all();
213
- return rows.map((r) => r.file_path);
420
+ function getCachedInsightEmbedding(repoRoot, lessonId, expectedHash) {
421
+ const database = openDb(repoRoot);
422
+ const row = database.prepare("SELECT embedding_insight, content_hash_insight FROM lessons WHERE id = ?").get(lessonId);
423
+ if (!row || !row.embedding_insight || !row.content_hash_insight) {
424
+ return null;
425
+ }
426
+ if (expectedHash && row.content_hash_insight !== expectedHash) {
427
+ return null;
428
+ }
429
+ return new Float32Array(
430
+ row.embedding_insight.buffer,
431
+ row.embedding_insight.byteOffset,
432
+ row.embedding_insight.byteLength / 4
433
+ );
214
434
  }
215
- function getChunkCountByFilePath(repoRoot, filePath) {
216
- const database = openKnowledgeDb(repoRoot);
217
- const row = database.prepare("SELECT COUNT(*) as cnt FROM chunks WHERE file_path = ?").get(filePath);
218
- return row.cnt;
435
+ function setCachedInsightEmbedding(repoRoot, lessonId, embedding, hash) {
436
+ const database = openDb(repoRoot);
437
+ const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
438
+ const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
439
+ database.prepare("UPDATE lessons SET embedding_insight = ?, content_hash_insight = ? WHERE id = ?").run(buffer, hash, lessonId);
219
440
  }
220
- function setLastIndexTime(repoRoot, time) {
221
- const database = openKnowledgeDb(repoRoot);
222
- database.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES ('last_index_time', ?)").run(time);
441
+ function collectCachedEmbeddings(database) {
442
+ const cache = /* @__PURE__ */ new Map();
443
+ const rows = database.prepare("SELECT id, embedding, content_hash, embedding_insight, content_hash_insight FROM lessons WHERE embedding IS NOT NULL OR embedding_insight IS NOT NULL").all();
444
+ for (const row of rows) {
445
+ if (row.embedding && row.content_hash) {
446
+ cache.set(row.id, {
447
+ embedding: row.embedding,
448
+ contentHash: row.content_hash,
449
+ embeddingInsight: row.embedding_insight,
450
+ contentHashInsight: row.content_hash_insight
451
+ });
452
+ } else if (row.embedding_insight && row.content_hash_insight) {
453
+ cache.set(row.id, {
454
+ embedding: row.embedding_insight,
455
+ // placeholder, won't match hash
456
+ contentHash: "",
457
+ embeddingInsight: row.embedding_insight,
458
+ contentHashInsight: row.content_hash_insight
459
+ });
460
+ }
461
+ }
462
+ return cache;
223
463
  }
224
- var init_sync = __esm({
225
- "src/memory/storage/sqlite-knowledge/sync.ts"() {
464
+ var init_cache = __esm({
465
+ "src/memory/storage/sqlite/cache.ts"() {
226
466
  init_connection();
227
467
  }
228
468
  });
229
- function isBinary(content) {
230
- return content.includes("\0");
231
- }
232
- function splitIntoSections(fileLines, ext) {
233
- if (ext === ".md") {
234
- return splitMarkdown(fileLines);
235
- }
236
- if (ext === ".rst") {
237
- return splitParagraphs(fileLines);
238
- }
239
- if (CODE_EXTENSIONS.has(ext)) {
240
- return splitCode(fileLines);
469
+ function getJsonlMtime(repoRoot) {
470
+ const jsonlPath = join(repoRoot, LESSONS_PATH);
471
+ try {
472
+ const stat = statSync(jsonlPath);
473
+ return stat.mtimeMs;
474
+ } catch {
475
+ return null;
241
476
  }
242
- return splitParagraphs(fileLines);
243
477
  }
244
- function splitMarkdown(fileLines) {
245
- const sections = [];
246
- let current = [];
247
- let inCodeBlock = false;
248
- for (let i = 0; i < fileLines.length; i++) {
249
- const line = fileLines[i];
250
- const lineObj = { lineNumber: i + 1, text: line };
251
- if (line.trimStart().startsWith("```")) {
252
- inCodeBlock = !inCodeBlock;
253
- current.push(lineObj);
254
- continue;
255
- }
256
- if (!inCodeBlock && /^#{2,}\s/.test(line) && current.length > 0) {
257
- sections.push(current);
258
- current = [lineObj];
259
- continue;
260
- }
261
- if (!inCodeBlock && line.trim() === "" && current.length > 0 && current.some((l) => l.text.trim() !== "")) {
262
- current.push(lineObj);
263
- sections.push(current);
264
- current = [];
265
- continue;
266
- }
267
- current.push(lineObj);
268
- }
269
- if (current.length > 0) {
270
- sections.push(current);
271
- }
272
- return sections;
478
+ function getLastSyncMtime(database) {
479
+ const row = database.prepare("SELECT value FROM metadata WHERE key = ?").get("last_sync_mtime");
480
+ return row ? parseFloat(row.value) : null;
273
481
  }
274
- function splitCode(fileLines) {
275
- const sections = [];
276
- let current = [];
277
- for (let i = 0; i < fileLines.length; i++) {
278
- const line = fileLines[i];
279
- const lineObj = { lineNumber: i + 1, text: line };
280
- if (line.trim() === "" && current.length > 0) {
281
- let hasNextNonBlank = false;
282
- for (let j = i + 1; j < fileLines.length; j++) {
283
- if (fileLines[j].trim() !== "") {
284
- hasNextNonBlank = true;
285
- break;
286
- }
287
- }
288
- if (hasNextNonBlank) {
289
- sections.push(current);
290
- current = [lineObj];
291
- continue;
292
- }
482
+ function setLastSyncMtime(database, mtime) {
483
+ database.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run("last_sync_mtime", mtime.toString());
484
+ }
485
+ async function rebuildIndex(repoRoot) {
486
+ const database = openDb(repoRoot);
487
+ const { items } = await readMemoryItems(repoRoot);
488
+ const cachedEmbeddings = collectCachedEmbeddings(database);
489
+ database.exec("DELETE FROM lessons");
490
+ if (items.length === 0) {
491
+ const mtime2 = getJsonlMtime(repoRoot);
492
+ if (mtime2 !== null) {
493
+ setLastSyncMtime(database, mtime2);
293
494
  }
294
- current.push(lineObj);
495
+ return;
295
496
  }
296
- if (current.length > 0) {
297
- sections.push(current);
497
+ const insert = database.prepare(INSERT_LESSON_SQL);
498
+ const insertMany = database.transaction((memoryItems) => {
499
+ for (const item of memoryItems) {
500
+ const newHash = contentHash(item.trigger, item.insight);
501
+ const insightHash = contentHash(item.insight, "");
502
+ const cached = cachedEmbeddings.get(item.id);
503
+ const hasValidCache = cached && cached.contentHash === newHash;
504
+ const hasValidInsightCache = cached && cached.contentHashInsight === insightHash;
505
+ insert.run({
506
+ id: item.id,
507
+ type: item.type,
508
+ trigger: item.trigger,
509
+ insight: item.insight,
510
+ evidence: item.evidence ?? null,
511
+ severity: item.severity ?? null,
512
+ tags: item.tags.join(","),
513
+ source: item.source,
514
+ context: JSON.stringify(item.context),
515
+ supersedes: JSON.stringify(item.supersedes),
516
+ related: JSON.stringify(item.related),
517
+ created: item.created,
518
+ confirmed: item.confirmed ? 1 : 0,
519
+ deleted: item.deleted ? 1 : 0,
520
+ retrieval_count: item.retrievalCount ?? 0,
521
+ last_retrieved: item.lastRetrieved ?? null,
522
+ embedding: hasValidCache ? cached.embedding : null,
523
+ content_hash: hasValidCache ? cached.contentHash : null,
524
+ embedding_insight: hasValidInsightCache ? cached.embeddingInsight : null,
525
+ content_hash_insight: hasValidInsightCache ? cached.contentHashInsight : null,
526
+ invalidated_at: item.invalidatedAt ?? null,
527
+ invalidation_reason: item.invalidationReason ?? null,
528
+ citation_file: item.citation?.file ?? null,
529
+ citation_line: item.citation?.line ?? null,
530
+ citation_commit: item.citation?.commit ?? null,
531
+ compaction_level: item.compactionLevel ?? 0,
532
+ compacted_at: item.compactedAt ?? null,
533
+ pattern_bad: item.pattern?.bad ?? null,
534
+ pattern_good: item.pattern?.good ?? null
535
+ });
536
+ }
537
+ });
538
+ insertMany(items);
539
+ const mtime = getJsonlMtime(repoRoot);
540
+ if (mtime !== null) {
541
+ setLastSyncMtime(database, mtime);
298
542
  }
299
- return sections;
300
543
  }
301
- function splitParagraphs(fileLines) {
302
- const sections = [];
303
- let current = [];
304
- for (let i = 0; i < fileLines.length; i++) {
305
- const line = fileLines[i];
306
- const lineObj = { lineNumber: i + 1, text: line };
307
- if (line.trim() === "" && current.length > 0) {
308
- sections.push(current);
309
- current = [lineObj];
310
- continue;
311
- }
312
- current.push(lineObj);
544
+ async function syncIfNeeded(repoRoot, options = {}) {
545
+ const { force = false } = options;
546
+ const jsonlMtime = getJsonlMtime(repoRoot);
547
+ if (jsonlMtime === null && !force) {
548
+ return false;
313
549
  }
314
- if (current.length > 0) {
315
- sections.push(current);
550
+ const database = openDb(repoRoot);
551
+ const lastSyncMtime = getLastSyncMtime(database);
552
+ const needsRebuild = force || lastSyncMtime === null || jsonlMtime !== null && jsonlMtime > lastSyncMtime;
553
+ if (needsRebuild) {
554
+ await rebuildIndex(repoRoot);
555
+ return true;
316
556
  }
317
- return sections;
557
+ return false;
318
558
  }
319
- function sectionText(section) {
320
- return section.map((l) => l.text).join("\n");
559
+ var INSERT_LESSON_SQL;
560
+ var init_sync = __esm({
561
+ "src/memory/storage/sqlite/sync.ts"() {
562
+ init_jsonl();
563
+ init_connection();
564
+ init_cache();
565
+ INSERT_LESSON_SQL = `
566
+ INSERT INTO lessons (id, type, trigger, insight, evidence, severity, tags, source, context, supersedes, related, created, confirmed, deleted, retrieval_count, last_retrieved, embedding, content_hash, embedding_insight, content_hash_insight, invalidated_at, invalidation_reason, citation_file, citation_line, citation_commit, compaction_level, compacted_at, pattern_bad, pattern_good)
567
+ VALUES (@id, @type, @trigger, @insight, @evidence, @severity, @tags, @source, @context, @supersedes, @related, @created, @confirmed, @deleted, @retrieval_count, @last_retrieved, @embedding, @content_hash, @embedding_insight, @content_hash_insight, @invalidated_at, @invalidation_reason, @citation_file, @citation_line, @citation_commit, @compaction_level, @compacted_at, @pattern_bad, @pattern_good)
568
+ `;
569
+ }
570
+ });
571
+
572
+ // src/memory/search/hybrid.ts
573
+ function normalizeBm25Rank(rank) {
574
+ if (!Number.isFinite(rank)) return 0;
575
+ const abs = Math.abs(rank);
576
+ return abs / (1 + abs);
321
577
  }
322
- function chunkFile(filePath, content, options) {
323
- if (content.trim() === "") return [];
324
- if (isBinary(content)) return [];
325
- const targetSize = options?.targetSize ?? DEFAULT_TARGET_SIZE;
326
- const overlapSize = options?.overlapSize ?? DEFAULT_OVERLAP_SIZE;
327
- const fileLines = content.split("\n");
328
- const ext = extname(filePath).toLowerCase();
329
- const sections = splitIntoSections(fileLines, ext);
330
- const chunks = [];
331
- let accumulated = [];
332
- let accumulatedLength = 0;
333
- function emitChunk(lines, overlapLines2) {
334
- if (lines.length === 0) return [];
335
- const allLines = [...overlapLines2, ...lines];
336
- const text = allLines.map((l) => l.text).join("\n");
337
- const startLine = allLines[0].lineNumber;
338
- const endLine = allLines[allLines.length - 1].lineNumber;
339
- chunks.push({
340
- id: generateChunkId(filePath, startLine, endLine),
341
- filePath,
342
- startLine,
343
- endLine,
344
- text,
345
- contentHash: chunkContentHash(text)
346
- });
347
- if (overlapSize <= 0) return [];
348
- const overlapResult = [];
349
- let overlapLen = 0;
350
- for (let i = lines.length - 1; i >= 0; i--) {
351
- const lineLen = lines[i].text.length + 1;
352
- if (overlapLen + lineLen > overlapSize && overlapResult.length > 0) break;
353
- overlapResult.unshift(lines[i]);
354
- overlapLen += lineLen;
355
- }
356
- return overlapResult;
578
+ function mergeHybridScores(vectorResults, keywordResults, getId, options) {
579
+ if (vectorResults.length === 0 && keywordResults.length === 0) return [];
580
+ const rawVecW = options?.vectorWeight ?? DEFAULT_VECTOR_WEIGHT;
581
+ const rawTxtW = options?.textWeight ?? DEFAULT_TEXT_WEIGHT;
582
+ const total = rawVecW + rawTxtW;
583
+ if (total <= 0) return [];
584
+ const vecW = rawVecW / total;
585
+ const txtW = rawTxtW / total;
586
+ const limit = options?.limit;
587
+ const minScore = options?.minScore;
588
+ const merged = /* @__PURE__ */ new Map();
589
+ for (const v of vectorResults) {
590
+ merged.set(getId(v.item), { item: v.item, vecScore: v.score, txtScore: 0 });
357
591
  }
358
- let overlapLines = [];
359
- for (const section of sections) {
360
- const sectionLen = sectionText(section).length;
361
- if (accumulatedLength > 0 && accumulatedLength + sectionLen > targetSize) {
362
- overlapLines = emitChunk(accumulated, overlapLines);
363
- accumulated = [];
364
- accumulatedLength = 0;
365
- }
366
- accumulated.push(...section);
367
- accumulatedLength += sectionLen;
368
- if (accumulatedLength > targetSize) {
369
- overlapLines = emitChunk(accumulated, overlapLines);
370
- accumulated = [];
371
- accumulatedLength = 0;
592
+ for (const k of keywordResults) {
593
+ const id = getId(k.item);
594
+ const existing = merged.get(id);
595
+ if (existing) {
596
+ existing.txtScore = k.score;
597
+ } else {
598
+ merged.set(id, { item: k.item, vecScore: 0, txtScore: k.score });
372
599
  }
373
600
  }
374
- if (accumulated.length > 0) {
375
- emitChunk(accumulated, overlapLines);
601
+ const results = [];
602
+ for (const entry of merged.values()) {
603
+ results.push({
604
+ item: entry.item,
605
+ score: vecW * entry.vecScore + txtW * entry.txtScore
606
+ });
376
607
  }
377
- return chunks;
608
+ results.sort((a, b) => b.score - a.score);
609
+ const filtered = minScore !== void 0 ? results.filter((r) => r.score >= minScore) : results;
610
+ return limit !== void 0 ? filtered.slice(0, limit) : filtered;
378
611
  }
379
- var DEFAULT_TARGET_SIZE, DEFAULT_OVERLAP_SIZE;
380
- var init_chunking = __esm({
381
- "src/memory/knowledge/chunking.ts"() {
382
- init_types();
383
- DEFAULT_TARGET_SIZE = 1600;
384
- DEFAULT_OVERLAP_SIZE = 320;
612
+ function mergeHybridResults(vectorResults, keywordResults, options) {
613
+ const genericVec = vectorResults.map((v) => ({ item: v.lesson, score: v.score }));
614
+ const genericKw = keywordResults.map((k) => ({ item: k.lesson, score: k.score }));
615
+ const merged = mergeHybridScores(genericVec, genericKw, (item) => item.id, options);
616
+ return merged.map((m) => ({ lesson: m.item, score: m.score }));
617
+ }
618
+ var DEFAULT_VECTOR_WEIGHT, DEFAULT_TEXT_WEIGHT, CANDIDATE_MULTIPLIER, MIN_HYBRID_SCORE;
619
+ var init_hybrid = __esm({
620
+ "src/memory/search/hybrid.ts"() {
621
+ DEFAULT_VECTOR_WEIGHT = 0.7;
622
+ DEFAULT_TEXT_WEIGHT = 0.3;
623
+ CANDIDATE_MULTIPLIER = 4;
624
+ MIN_HYBRID_SCORE = 0.35;
385
625
  }
386
626
  });
387
- function fileHash(content) {
388
- return createHash("sha256").update(content).digest("hex");
389
- }
390
- function fileHashKey(relativePath) {
391
- return "file_hash:" + relativePath;
392
- }
393
- function getStoredFileHash(repoRoot, relativePath) {
394
- const db = openKnowledgeDb(repoRoot);
395
- const row = db.prepare("SELECT value FROM metadata WHERE key = ?").get(fileHashKey(relativePath));
396
- return row?.value ?? null;
397
- }
398
- function setFileHash(repoRoot, relativePath, hash) {
399
- const db = openKnowledgeDb(repoRoot);
400
- db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run(fileHashKey(relativePath), hash);
401
- }
402
- function removeFileHash(repoRoot, relativePath) {
403
- const db = openKnowledgeDb(repoRoot);
404
- db.prepare("DELETE FROM metadata WHERE key = ?").run(fileHashKey(relativePath));
405
- }
406
- async function walkSupportedFiles(baseDir, repoRoot) {
407
- const results = [];
408
- let entries;
627
+
628
+ // src/memory/storage/sqlite/search.ts
629
+ function safeJsonParse(value, fallback) {
409
630
  try {
410
- entries = await readdir(baseDir, { recursive: true, withFileTypes: true });
631
+ return JSON.parse(value);
411
632
  } catch {
412
- return results;
413
- }
414
- for (const entry of entries) {
415
- if (!entry.isFile()) continue;
416
- const ext = extname(entry.name).toLowerCase();
417
- if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
418
- const fullPath = join(entry.parentPath ?? entry.path, entry.name);
419
- const relPath = relative(repoRoot, fullPath);
420
- results.push(relPath);
633
+ return fallback;
421
634
  }
422
- return results;
423
635
  }
424
- async function indexDocs(repoRoot, options = {}) {
425
- const start = Date.now();
426
- const docsDir = options.docsDir ?? "docs";
427
- const force = options.force ?? false;
428
- const stats = {
429
- filesIndexed: 0,
430
- filesSkipped: 0,
431
- filesErrored: 0,
432
- chunksCreated: 0,
433
- chunksDeleted: 0,
434
- durationMs: 0
636
+ function rowToMemoryItem(row) {
637
+ const item = {
638
+ id: row.id,
639
+ type: row.type,
640
+ trigger: row.trigger,
641
+ insight: row.insight,
642
+ tags: row.tags ? row.tags.split(",").filter(Boolean) : [],
643
+ source: row.source,
644
+ context: safeJsonParse(row.context, {}),
645
+ supersedes: safeJsonParse(row.supersedes, []),
646
+ related: safeJsonParse(row.related, []),
647
+ created: row.created,
648
+ confirmed: row.confirmed === 1
435
649
  };
436
- const docsPath = join(repoRoot, docsDir);
437
- const filePaths = await walkSupportedFiles(docsPath, repoRoot);
438
- for (const relPath of filePaths) {
439
- const fullPath = join(repoRoot, relPath);
440
- let content;
441
- try {
442
- content = await readFile(fullPath, "utf-8");
443
- } catch {
444
- stats.filesErrored++;
445
- continue;
446
- }
447
- const hash = fileHash(content);
448
- const storedHash = getStoredFileHash(repoRoot, relPath);
449
- if (!force && storedHash === hash) {
450
- stats.filesSkipped++;
451
- continue;
452
- }
453
- const chunks = chunkFile(relPath, content);
454
- const now = (/* @__PURE__ */ new Date()).toISOString();
455
- const knowledgeChunks = chunks.map((chunk) => ({
456
- id: chunk.id,
457
- filePath: chunk.filePath,
458
- startLine: chunk.startLine,
459
- endLine: chunk.endLine,
460
- contentHash: chunk.contentHash,
461
- text: chunk.text,
462
- updatedAt: now
463
- }));
464
- const db = openKnowledgeDb(repoRoot);
465
- db.transaction(() => {
466
- deleteChunksByFilePath(repoRoot, [relPath]);
467
- if (knowledgeChunks.length > 0) {
468
- upsertChunks(repoRoot, knowledgeChunks);
469
- }
470
- setFileHash(repoRoot, relPath, hash);
471
- })();
472
- stats.filesIndexed++;
473
- stats.chunksCreated += knowledgeChunks.length;
650
+ if (row.evidence !== null) item.evidence = row.evidence;
651
+ if (row.severity !== null) item.severity = row.severity;
652
+ if (row.deleted === 1) item.deleted = true;
653
+ if (row.retrieval_count > 0) item.retrievalCount = row.retrieval_count;
654
+ if (row.invalidated_at !== null) item.invalidatedAt = row.invalidated_at;
655
+ if (row.invalidation_reason !== null) item.invalidationReason = row.invalidation_reason;
656
+ if (row.citation_file !== null) {
657
+ item.citation = {
658
+ file: row.citation_file,
659
+ ...row.citation_line !== null && { line: row.citation_line },
660
+ ...row.citation_commit !== null && { commit: row.citation_commit }
661
+ };
474
662
  }
475
- const indexedPaths = getIndexedFilePaths(repoRoot);
476
- const currentPathSet = new Set(filePaths);
477
- const stalePaths = indexedPaths.filter((p) => !currentPathSet.has(p));
478
- if (stalePaths.length > 0) {
479
- for (const path2 of stalePaths) {
480
- stats.chunksDeleted += getChunkCountByFilePath(repoRoot, path2);
481
- }
482
- deleteChunksByFilePath(repoRoot, stalePaths);
483
- for (const path2 of stalePaths) {
484
- removeFileHash(repoRoot, path2);
485
- }
663
+ if (row.compaction_level !== null && row.compaction_level !== 0) {
664
+ item.compactionLevel = row.compaction_level;
486
665
  }
487
- setLastIndexTime(repoRoot, (/* @__PURE__ */ new Date()).toISOString());
488
- stats.durationMs = Date.now() - start;
489
- return stats;
490
- }
491
- var init_indexing = __esm({
492
- "src/memory/knowledge/indexing.ts"() {
493
- init_connection();
494
- init_sync();
495
- init_chunking();
496
- init_types();
666
+ if (row.compacted_at !== null) item.compactedAt = row.compacted_at;
667
+ if (row.last_retrieved !== null) item.lastRetrieved = row.last_retrieved;
668
+ if (row.pattern_bad !== null && row.pattern_good !== null) {
669
+ item.pattern = { bad: row.pattern_bad, good: row.pattern_good };
497
670
  }
498
- });
499
- var _require = createRequire(import.meta.url);
500
- var _pkg = _require("../package.json");
501
- var VERSION = _pkg.version;
502
- var SourceSchema = z.enum([
503
- "user_correction",
504
- "self_correction",
505
- "test_failure",
506
- "manual"
507
- ]);
508
- var ContextSchema = z.object({
509
- tool: z.string(),
510
- intent: z.string()
511
- });
512
- var PatternSchema = z.object({
513
- bad: z.string(),
514
- good: z.string()
515
- });
516
- var CitationSchema = z.object({
517
- file: z.string().min(1),
518
- // Source file path (required, non-empty)
519
- line: z.number().int().positive().optional(),
520
- // Line number (optional, must be positive)
521
- commit: z.string().optional()
522
- // Git commit hash (optional)
523
- });
524
- var SeveritySchema = z.enum(["high", "medium", "low"]);
525
- var CompactionLevelSchema = z.union([
526
- z.literal(0),
527
- // Active
528
- z.literal(1),
529
- // Flagged (>90 days)
530
- z.literal(2)
531
- // Archived
532
- ]);
533
- var LessonTypeSchema = z.enum(["quick", "full"]);
534
- var MemoryItemTypeSchema = z.enum(["lesson", "solution", "pattern", "preference"]);
535
- var baseFields = {
536
- // Core identity (required)
537
- id: z.string(),
538
- trigger: z.string(),
539
- insight: z.string(),
540
- // Metadata (required)
541
- tags: z.array(z.string()),
542
- source: SourceSchema,
543
- context: ContextSchema,
544
- created: z.string(),
545
- // ISO8601
546
- confirmed: z.boolean(),
547
- // Relationships (required, can be empty arrays)
548
- supersedes: z.array(z.string()),
549
- related: z.array(z.string()),
550
- // Extended fields (optional)
551
- evidence: z.string().optional(),
552
- severity: SeveritySchema.optional(),
553
- // Lifecycle fields (optional)
554
- deleted: z.boolean().optional(),
555
- deletedAt: z.string().optional(),
556
- retrievalCount: z.number().optional(),
557
- // Provenance tracking (optional)
558
- citation: CitationSchema.optional(),
559
- // Age-based validity fields (optional)
560
- compactionLevel: CompactionLevelSchema.optional(),
561
- compactedAt: z.string().optional(),
562
- lastRetrieved: z.string().optional(),
563
- // Invalidation fields (optional)
564
- invalidatedAt: z.string().optional(),
565
- invalidationReason: z.string().optional()
566
- };
567
- var LessonItemSchema = z.object({
568
- ...baseFields,
569
- type: z.literal("lesson"),
570
- pattern: PatternSchema.optional()
571
- });
572
- var SolutionItemSchema = z.object({
573
- ...baseFields,
574
- type: z.literal("solution"),
575
- pattern: PatternSchema.optional()
576
- });
577
- var PatternItemSchema = z.object({
578
- ...baseFields,
579
- type: z.literal("pattern"),
580
- pattern: PatternSchema
581
- });
582
- var PreferenceItemSchema = z.object({
583
- ...baseFields,
584
- type: z.literal("preference"),
585
- pattern: PatternSchema.optional()
586
- });
587
- var MemoryItemSchema = z.discriminatedUnion("type", [
588
- LessonItemSchema,
589
- SolutionItemSchema,
590
- PatternItemSchema,
591
- PreferenceItemSchema
592
- ]);
593
- var LegacyLessonSchema = z.object({
594
- ...baseFields,
595
- type: LessonTypeSchema,
596
- pattern: PatternSchema.optional()
597
- });
598
- var LessonSchema = LessonItemSchema;
599
- var LegacyTombstoneSchema = z.object({
600
- id: z.string(),
601
- deleted: z.literal(true),
602
- deletedAt: z.string()
603
- // ISO8601
604
- });
605
- var LessonRecordSchema = z.union([
606
- MemoryItemSchema,
607
- LegacyLessonSchema,
608
- LegacyTombstoneSchema
609
- ]);
610
- var MemoryItemRecordSchema = LessonRecordSchema;
611
- var TYPE_PREFIXES = {
612
- lesson: "L",
613
- solution: "S",
614
- pattern: "P",
615
- preference: "R"
616
- };
617
- function generateId(insight, type) {
618
- const prefix = TYPE_PREFIXES[type ?? "lesson"];
619
- const hash = createHash("sha256").update(insight).digest("hex");
620
- return `${prefix}${hash.slice(0, 8)}`;
671
+ const result = MemoryItemSchema.safeParse(item);
672
+ if (!result.success) return null;
673
+ return result.data;
621
674
  }
622
-
623
- // src/memory/storage/jsonl.ts
624
- var LESSONS_PATH = ".claude/lessons/index.jsonl";
625
- async function appendMemoryItem(repoRoot, item) {
626
- const filePath = join(repoRoot, LESSONS_PATH);
627
- await mkdir(dirname(filePath), { recursive: true });
628
- const line = JSON.stringify(item) + "\n";
629
- await appendFile(filePath, line, "utf-8");
675
+ function readAllFromSqlite(repoRoot) {
676
+ const database = openDb(repoRoot);
677
+ const rows = database.prepare("SELECT * FROM lessons WHERE invalidated_at IS NULL").all();
678
+ return rows.map(rowToMemoryItem).filter((x) => x !== null);
630
679
  }
631
- async function appendLesson(repoRoot, lesson) {
632
- return appendMemoryItem(repoRoot, lesson);
680
+ function sanitizeFtsQuery(query) {
681
+ const stripped = query.replace(/["*^+\-():{}]/g, "");
682
+ const tokens = stripped.split(/\s+/).filter((t) => t.length > 0 && !FTS_OPERATORS.has(t));
683
+ return tokens.join(" ");
633
684
  }
634
- function parseJsonLine(line, lineNumber, strict, onParseError) {
635
- let parsed;
685
+ function incrementRetrievalCount(repoRoot, lessonIds) {
686
+ if (lessonIds.length === 0) return;
687
+ const database = openDb(repoRoot);
688
+ const now = (/* @__PURE__ */ new Date()).toISOString();
689
+ const update = database.prepare(`
690
+ UPDATE lessons
691
+ SET retrieval_count = retrieval_count + 1,
692
+ last_retrieved = ?
693
+ WHERE id = ?
694
+ `);
695
+ const updateMany = database.transaction((ids) => {
696
+ for (const id of ids) {
697
+ update.run(now, id);
698
+ }
699
+ });
700
+ updateMany(lessonIds);
701
+ }
702
+ function executeFtsQuery(repoRoot, query, limit, options) {
703
+ const database = openDb(repoRoot);
704
+ const sanitized = sanitizeFtsQuery(query);
705
+ if (sanitized === "") return [];
706
+ const selectCols = options.includeRank ? "l.*, fts.rank" : "l.*";
707
+ const orderClause = options.includeRank ? "ORDER BY fts.rank" : "";
708
+ const typeClause = options.typeFilter ? "AND l.type = ?" : "";
709
+ const sql = `
710
+ SELECT ${selectCols}
711
+ FROM lessons l
712
+ JOIN lessons_fts fts ON l.rowid = fts.rowid
713
+ WHERE lessons_fts MATCH ?
714
+ AND l.invalidated_at IS NULL
715
+ ${typeClause}
716
+ ${orderClause}
717
+ LIMIT ?
718
+ `;
719
+ const params = options.typeFilter ? [sanitized, options.typeFilter, limit] : [sanitized, limit];
636
720
  try {
637
- parsed = JSON.parse(line);
721
+ return database.prepare(sql).all(...params);
638
722
  } catch (err) {
639
- const parseError = {
640
- line: lineNumber,
641
- message: `Invalid JSON: ${err.message}`,
642
- cause: err
643
- };
644
- if (strict) {
645
- throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
646
- }
647
- onParseError?.(parseError);
648
- return null;
723
+ const message = err instanceof Error ? err.message : "Unknown FTS5 error";
724
+ console.error(`[compound-agent] search error: ${message}`);
725
+ return [];
649
726
  }
650
- const result = MemoryItemRecordSchema.safeParse(parsed);
651
- if (!result.success) {
652
- const parseError = {
653
- line: lineNumber,
654
- message: `Schema validation failed: ${result.error.message}`,
655
- cause: result.error
656
- };
657
- if (strict) {
658
- throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
727
+ }
728
+ async function searchKeyword(repoRoot, query, limit, typeFilter) {
729
+ const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: false, typeFilter });
730
+ return rows.map(rowToMemoryItem).filter((x) => x !== null);
731
+ }
732
+ async function searchKeywordScored(repoRoot, query, limit, typeFilter) {
733
+ const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: true, typeFilter });
734
+ const results = [];
735
+ for (const row of rows) {
736
+ const lesson = rowToMemoryItem(row);
737
+ if (lesson) {
738
+ results.push({ lesson, score: normalizeBm25Rank(row.rank) });
659
739
  }
660
- onParseError?.(parseError);
661
- return null;
662
740
  }
663
- return result.data;
741
+ return results;
664
742
  }
665
- function toMemoryItem(record) {
666
- if (record.deleted === true) {
667
- return null;
743
+ var FTS_OPERATORS;
744
+ var init_search = __esm({
745
+ "src/memory/storage/sqlite/search.ts"() {
746
+ init_types();
747
+ init_hybrid();
748
+ init_connection();
749
+ FTS_OPERATORS = /* @__PURE__ */ new Set(["AND", "OR", "NOT", "NEAR"]);
668
750
  }
669
- if (record.type === "quick" || record.type === "full") {
670
- return { ...record, type: "lesson" };
751
+ });
752
+
753
+ // src/memory/storage/sqlite/index.ts
754
+ var init_sqlite = __esm({
755
+ "src/memory/storage/sqlite/index.ts"() {
756
+ init_connection();
757
+ init_cache();
758
+ init_sync();
759
+ init_availability();
760
+ init_search();
671
761
  }
672
- return record;
762
+ });
763
+ var init_compact = __esm({
764
+ "src/memory/storage/compact.ts"() {
765
+ init_types();
766
+ init_jsonl();
767
+ }
768
+ });
769
+
770
+ // src/memory/storage/index.ts
771
+ var init_storage = __esm({
772
+ "src/memory/storage/index.ts"() {
773
+ init_jsonl();
774
+ init_sqlite();
775
+ init_compact();
776
+ }
777
+ });
778
+
779
+ // src/memory/embeddings/model.ts
780
+ var model_exports = {};
781
+ __export(model_exports, {
782
+ MODEL_FILENAME: () => MODEL_FILENAME,
783
+ MODEL_URI: () => MODEL_URI,
784
+ clearUsabilityCache: () => clearUsabilityCache,
785
+ isModelAvailable: () => isModelAvailable,
786
+ isModelUsable: () => isModelUsable,
787
+ resolveModel: () => resolveModel
788
+ });
789
+ function isModelAvailable() {
790
+ return existsSync(join(DEFAULT_MODEL_DIR, MODEL_FILENAME));
673
791
  }
674
- async function readMemoryItems(repoRoot, options = {}) {
675
- const { strict = false, onParseError } = options;
676
- const filePath = join(repoRoot, LESSONS_PATH);
677
- let content;
792
+ async function isModelUsable() {
793
+ if (cachedUsability !== null) {
794
+ return cachedUsability;
795
+ }
796
+ if (!isModelAvailable()) {
797
+ cachedUsability = {
798
+ usable: false,
799
+ reason: "Embedding model file not found",
800
+ action: "Run: npx ca download-model"
801
+ };
802
+ return cachedUsability;
803
+ }
804
+ let llama = null;
805
+ let model = null;
806
+ let context = null;
678
807
  try {
679
- content = await readFile(filePath, "utf-8");
808
+ const modelPath = join(DEFAULT_MODEL_DIR, MODEL_FILENAME);
809
+ llama = await getLlama({
810
+ build: "never",
811
+ // Never compile from source in a deployed tool
812
+ progressLogs: false,
813
+ // Suppress prebuilt binary fallback warnings
814
+ logLevel: LlamaLogLevel.error
815
+ // Only surface real errors from C++ backend
816
+ // Set NODE_LLAMA_CPP_DEBUG=true to re-enable all output for troubleshooting
817
+ });
818
+ model = await llama.loadModel({ modelPath });
819
+ context = await model.createEmbeddingContext();
820
+ cachedUsability = { usable: true };
821
+ return cachedUsability;
680
822
  } catch (err) {
681
- if (err.code === "ENOENT") {
682
- return { items: [], skippedCount: 0 };
823
+ const message = err instanceof Error ? err.message : "Unknown error";
824
+ cachedUsability = {
825
+ usable: false,
826
+ reason: `Embedding model runtime initialization failed: ${message}`,
827
+ action: "Check system compatibility or reinstall: npx ca download-model"
828
+ };
829
+ return cachedUsability;
830
+ } finally {
831
+ if (context) {
832
+ try {
833
+ await context.dispose();
834
+ } catch {
835
+ }
683
836
  }
684
- throw err;
685
- }
686
- const items = /* @__PURE__ */ new Map();
687
- let skippedCount = 0;
688
- const lines = content.split("\n");
689
- for (let i = 0; i < lines.length; i++) {
690
- const trimmed = lines[i].trim();
691
- if (!trimmed) continue;
692
- const record = parseJsonLine(trimmed, i + 1, strict, onParseError);
693
- if (!record) {
694
- skippedCount++;
695
- continue;
837
+ if (model) {
838
+ try {
839
+ await model.dispose();
840
+ } catch {
841
+ }
696
842
  }
697
- if (record.deleted === true) {
698
- items.delete(record.id);
699
- } else {
700
- const item = toMemoryItem(record);
701
- if (item) {
702
- items.set(record.id, item);
843
+ if (llama) {
844
+ try {
845
+ await llama.dispose();
846
+ } catch {
703
847
  }
704
848
  }
705
849
  }
706
- return { items: Array.from(items.values()), skippedCount };
707
850
  }
708
- async function readLessons(repoRoot, options = {}) {
709
- const result = await readMemoryItems(repoRoot, options);
710
- const lessons = result.items.filter((item) => item.type === "lesson");
711
- return { lessons, skippedCount: result.skippedCount };
851
+ function clearUsabilityCache() {
852
+ cachedUsability = null;
853
+ }
854
+ async function resolveModel(options = {}) {
855
+ const { cli = true } = options;
856
+ return resolveModelFile(MODEL_URI, { cli });
857
+ }
858
+ var MODEL_URI, MODEL_FILENAME, DEFAULT_MODEL_DIR, cachedUsability;
859
+ var init_model = __esm({
860
+ "src/memory/embeddings/model.ts"() {
861
+ MODEL_URI = "hf:ggml-org/embeddinggemma-300M-qat-q4_0-GGUF/embeddinggemma-300M-qat-Q4_0.gguf";
862
+ MODEL_FILENAME = "hf_ggml-org_embeddinggemma-300M-qat-Q4_0.gguf";
863
+ DEFAULT_MODEL_DIR = join(homedir(), ".node-llama-cpp", "models");
864
+ cachedUsability = null;
865
+ }
866
+ });
867
+ async function getEmbedding() {
868
+ if (embeddingContext) return embeddingContext;
869
+ if (pendingInit) return pendingInit;
870
+ pendingInit = (async () => {
871
+ try {
872
+ const modelPath = await resolveModel({ cli: true });
873
+ llamaInstance = await getLlama({
874
+ build: "never",
875
+ // Never compile from source in a deployed tool
876
+ progressLogs: false,
877
+ // Suppress prebuilt binary fallback warnings
878
+ logLevel: LlamaLogLevel.error
879
+ // Only surface real errors from C++ backend
880
+ // Set NODE_LLAMA_CPP_DEBUG=true to re-enable all output for troubleshooting
881
+ });
882
+ modelInstance = await llamaInstance.loadModel({ modelPath });
883
+ embeddingContext = await modelInstance.createEmbeddingContext();
884
+ return embeddingContext;
885
+ } catch (err) {
886
+ pendingInit = null;
887
+ throw err;
888
+ }
889
+ })();
890
+ return pendingInit;
891
+ }
892
+ function unloadEmbedding() {
893
+ if (embeddingContext) {
894
+ embeddingContext.dispose().catch(() => {
895
+ });
896
+ embeddingContext = null;
897
+ }
898
+ if (modelInstance) {
899
+ modelInstance.dispose().catch(() => {
900
+ });
901
+ modelInstance = null;
902
+ }
903
+ if (llamaInstance) {
904
+ llamaInstance.dispose().catch(() => {
905
+ });
906
+ llamaInstance = null;
907
+ }
908
+ pendingInit = null;
909
+ }
910
+ async function embedText(text) {
911
+ const ctx = await getEmbedding();
912
+ const result = await ctx.getEmbeddingFor(text);
913
+ return new Float32Array(result.vector);
914
+ }
915
+ async function embedTexts(texts) {
916
+ if (texts.length === 0) return [];
917
+ const ctx = await getEmbedding();
918
+ const results = [];
919
+ for (const text of texts) {
920
+ const result = await ctx.getEmbeddingFor(text);
921
+ results.push(new Float32Array(result.vector));
922
+ }
923
+ return results;
712
924
  }
925
+ var embeddingContext, pendingInit, llamaInstance, modelInstance;
926
+ var init_nomic = __esm({
927
+ "src/memory/embeddings/nomic.ts"() {
928
+ init_model();
929
+ embeddingContext = null;
930
+ pendingInit = null;
931
+ llamaInstance = null;
932
+ modelInstance = null;
933
+ }
934
+ });
713
935
 
714
- // src/memory/storage/sqlite/connection.ts
715
- init_availability();
936
+ // src/memory/embeddings/index.ts
937
+ var init_embeddings = __esm({
938
+ "src/memory/embeddings/index.ts"() {
939
+ init_nomic();
940
+ init_model();
941
+ }
942
+ });
716
943
 
717
- // src/memory/storage/sqlite/schema.ts
718
- var SCHEMA_VERSION = 3;
719
- var SCHEMA_SQL = `
720
- CREATE TABLE IF NOT EXISTS lessons (
721
- id TEXT PRIMARY KEY,
722
- type TEXT NOT NULL,
723
- trigger TEXT NOT NULL,
724
- insight TEXT NOT NULL,
725
- evidence TEXT,
726
- severity TEXT,
727
- tags TEXT NOT NULL DEFAULT '',
728
- source TEXT NOT NULL,
729
- context TEXT NOT NULL DEFAULT '{}',
730
- supersedes TEXT NOT NULL DEFAULT '[]',
731
- related TEXT NOT NULL DEFAULT '[]',
732
- created TEXT NOT NULL,
733
- confirmed INTEGER NOT NULL DEFAULT 0,
734
- deleted INTEGER NOT NULL DEFAULT 0,
735
- retrieval_count INTEGER NOT NULL DEFAULT 0,
736
- last_retrieved TEXT,
944
+ // src/compound/clustering.ts
945
+ function buildSimilarityMatrix(embeddings) {
946
+ const n = embeddings.length;
947
+ const matrix = Array.from({ length: n }, () => new Array(n).fill(0));
948
+ for (let i = 0; i < n; i++) {
949
+ matrix[i][i] = 1;
950
+ for (let j = i + 1; j < n; j++) {
951
+ const sim = cosineSimilarity(embeddings[i], embeddings[j]);
952
+ matrix[i][j] = sim;
953
+ matrix[j][i] = sim;
954
+ }
955
+ }
956
+ return matrix;
957
+ }
958
+ function clusterBySimilarity(items, embeddings, threshold = DEFAULT_THRESHOLD) {
959
+ const n = items.length;
960
+ if (n === 0) return { clusters: [], noise: [] };
961
+ const matrix = buildSimilarityMatrix(embeddings);
962
+ const parent = Array.from({ length: n }, (_, i) => i);
963
+ function find(x) {
964
+ while (parent[x] !== x) {
965
+ parent[x] = parent[parent[x]];
966
+ x = parent[x];
967
+ }
968
+ return x;
969
+ }
970
+ function union(a, b) {
971
+ const rootA = find(a);
972
+ const rootB = find(b);
973
+ if (rootA !== rootB) parent[rootA] = rootB;
974
+ }
975
+ for (let i = 0; i < n; i++) {
976
+ for (let j = i + 1; j < n; j++) {
977
+ if (matrix[i][j] >= threshold) {
978
+ union(i, j);
979
+ }
980
+ }
981
+ }
982
+ const groups = /* @__PURE__ */ new Map();
983
+ for (let i = 0; i < n; i++) {
984
+ const root = find(i);
985
+ let group = groups.get(root);
986
+ if (!group) {
987
+ group = [];
988
+ groups.set(root, group);
989
+ }
990
+ group.push(items[i]);
991
+ }
992
+ const clusters = Array.from(groups.values());
993
+ return { clusters, noise: [] };
994
+ }
995
+ var DEFAULT_THRESHOLD;
996
+ var init_clustering = __esm({
997
+ "src/compound/clustering.ts"() {
998
+ init_search2();
999
+ DEFAULT_THRESHOLD = 0.75;
1000
+ }
1001
+ });
1002
+ function generateCctId(input) {
1003
+ const hash = createHash("sha256").update(input).digest("hex");
1004
+ return `CCT-${hash.slice(0, 8)}`;
1005
+ }
1006
+ var CCT_PATTERNS_PATH, CctPatternSchema;
1007
+ var init_types2 = __esm({
1008
+ "src/compound/types.ts"() {
1009
+ CCT_PATTERNS_PATH = ".claude/lessons/cct-patterns.jsonl";
1010
+ CctPatternSchema = z.object({
1011
+ id: z.string().regex(/^CCT-[a-f0-9]{8}$/),
1012
+ name: z.string().min(1),
1013
+ description: z.string().min(1),
1014
+ frequency: z.number().int().positive(),
1015
+ testable: z.boolean(),
1016
+ testApproach: z.string().optional(),
1017
+ sourceIds: z.array(z.string()).min(1),
1018
+ created: z.string()
1019
+ // ISO8601
1020
+ });
1021
+ }
1022
+ });
1023
+ async function readCctPatterns(repoRoot) {
1024
+ const filePath = join(repoRoot, CCT_PATTERNS_PATH);
1025
+ let content;
1026
+ try {
1027
+ content = await readFile(filePath, "utf-8");
1028
+ } catch (err) {
1029
+ if (err.code === "ENOENT") {
1030
+ return [];
1031
+ }
1032
+ throw err;
1033
+ }
1034
+ const patterns = [];
1035
+ const lines = content.split("\n");
1036
+ for (const line of lines) {
1037
+ const trimmed = line.trim();
1038
+ if (!trimmed) continue;
1039
+ const parsed = JSON.parse(trimmed);
1040
+ const result = CctPatternSchema.safeParse(parsed);
1041
+ if (result.success) {
1042
+ patterns.push(result.data);
1043
+ }
1044
+ }
1045
+ return patterns;
1046
+ }
1047
+ async function writeCctPatterns(repoRoot, patterns) {
1048
+ const filePath = join(repoRoot, CCT_PATTERNS_PATH);
1049
+ await mkdir(dirname(filePath), { recursive: true });
1050
+ const lines = patterns.map((p) => JSON.stringify(p) + "\n").join("");
1051
+ await appendFile(filePath, lines, "utf-8");
1052
+ }
1053
+ var init_io = __esm({
1054
+ "src/compound/io.ts"() {
1055
+ init_types2();
1056
+ }
1057
+ });
1058
+
1059
+ // src/compound/synthesis.ts
1060
+ function synthesizePattern(cluster, clusterId) {
1061
+ const id = generateCctId(clusterId);
1062
+ const frequency = cluster.length;
1063
+ const sourceIds = cluster.map((item) => item.id);
1064
+ const tagCounts = /* @__PURE__ */ new Map();
1065
+ for (const item of cluster) {
1066
+ for (const tag of item.tags) {
1067
+ tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
1068
+ }
1069
+ }
1070
+ const sortedTags = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).map(([tag]) => tag);
1071
+ const name = sortedTags.length > 0 ? sortedTags.slice(0, 3).join(", ") : cluster[0].insight.slice(0, 50);
1072
+ const description = cluster.map((item) => item.insight).join("; ");
1073
+ const hasHighSeverity = cluster.some(
1074
+ (item) => "severity" in item && item.severity === "high"
1075
+ );
1076
+ const hasEvidence = cluster.some(
1077
+ (item) => "evidence" in item && item.evidence
1078
+ );
1079
+ const testable = hasHighSeverity || hasEvidence;
1080
+ const testApproach = testable ? `Verify pattern: ${name}. Check ${frequency} related lesson(s).` : void 0;
1081
+ return {
1082
+ id,
1083
+ name,
1084
+ description,
1085
+ frequency,
1086
+ testable,
1087
+ ...testApproach !== void 0 && { testApproach },
1088
+ sourceIds,
1089
+ created: (/* @__PURE__ */ new Date()).toISOString()
1090
+ };
1091
+ }
1092
+ var init_synthesis = __esm({
1093
+ "src/compound/synthesis.ts"() {
1094
+ init_types2();
1095
+ }
1096
+ });
1097
+
1098
+ // src/compound/index.ts
1099
+ var init_compound = __esm({
1100
+ "src/compound/index.ts"() {
1101
+ init_clustering();
1102
+ init_io();
1103
+ init_synthesis();
1104
+ init_types2();
1105
+ }
1106
+ });
1107
+
1108
+ // src/memory/search/vector.ts
1109
+ function cosineSimilarity(a, b) {
1110
+ if (a.length !== b.length) {
1111
+ throw new Error("Vectors must have same length");
1112
+ }
1113
+ let dotProduct = 0;
1114
+ let normA = 0;
1115
+ let normB = 0;
1116
+ for (let i = 0; i < a.length; i++) {
1117
+ dotProduct += a[i] * b[i];
1118
+ normA += a[i] * a[i];
1119
+ normB += b[i] * b[i];
1120
+ }
1121
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
1122
+ if (magnitude === 0) return 0;
1123
+ return dotProduct / magnitude;
1124
+ }
1125
+ function cctToMemoryItem(pattern) {
1126
+ return {
1127
+ id: pattern.id,
1128
+ type: "lesson",
1129
+ trigger: pattern.name,
1130
+ insight: pattern.description,
1131
+ tags: [],
1132
+ source: "manual",
1133
+ context: { tool: "compound", intent: "synthesis" },
1134
+ created: pattern.created,
1135
+ confirmed: true,
1136
+ supersedes: [],
1137
+ related: pattern.sourceIds
1138
+ };
1139
+ }
1140
+ async function searchVector(repoRoot, query, options) {
1141
+ const limit = options?.limit ?? DEFAULT_LIMIT;
1142
+ await syncIfNeeded(repoRoot);
1143
+ const items = readAllFromSqlite(repoRoot);
1144
+ let cctPatterns = [];
1145
+ try {
1146
+ cctPatterns = await readCctPatterns(repoRoot);
1147
+ } catch {
1148
+ }
1149
+ if (items.length === 0 && cctPatterns.length === 0) return [];
1150
+ const queryVector = await embedText(query);
1151
+ const cachedEmbeddings = getCachedEmbeddingsBulk(repoRoot);
1152
+ const scored = [];
1153
+ for (const item of items) {
1154
+ if (item.invalidatedAt) continue;
1155
+ try {
1156
+ const itemText = `${item.trigger} ${item.insight}`;
1157
+ const hash = contentHash(item.trigger, item.insight);
1158
+ const cached = cachedEmbeddings.get(item.id);
1159
+ let itemVector;
1160
+ if (cached && cached.hash === hash) {
1161
+ itemVector = cached.vector;
1162
+ } else {
1163
+ itemVector = await embedText(itemText);
1164
+ setCachedEmbedding(repoRoot, item.id, itemVector, hash);
1165
+ }
1166
+ const score = cosineSimilarity(queryVector, itemVector);
1167
+ scored.push({ lesson: item, score });
1168
+ } catch {
1169
+ continue;
1170
+ }
1171
+ }
1172
+ for (const pattern of cctPatterns) {
1173
+ try {
1174
+ const text = `${pattern.name} ${pattern.description}`;
1175
+ const hash = contentHash(pattern.name, pattern.description);
1176
+ const cacheKey = `${pattern.id}:${hash}`;
1177
+ let vec = cctEmbeddingCache.get(cacheKey);
1178
+ if (!vec) {
1179
+ vec = await embedText(text);
1180
+ cctEmbeddingCache.set(cacheKey, vec);
1181
+ }
1182
+ const score = cosineSimilarity(queryVector, vec);
1183
+ scored.push({ lesson: cctToMemoryItem(pattern), score });
1184
+ } catch {
1185
+ continue;
1186
+ }
1187
+ }
1188
+ scored.sort((a, b) => b.score - a.score);
1189
+ return scored.slice(0, limit);
1190
+ }
1191
+ async function findSimilarLessons(repoRoot, text, options) {
1192
+ const threshold = options?.threshold ?? DEFAULT_THRESHOLD2;
1193
+ const excludeId = options?.excludeId;
1194
+ if (!isModelAvailable()) return [];
1195
+ let items;
1196
+ if (options?.items) {
1197
+ items = options.items;
1198
+ } else {
1199
+ await syncIfNeeded(repoRoot);
1200
+ items = readAllFromSqlite(repoRoot);
1201
+ }
1202
+ if (items.length === 0) return [];
1203
+ const queryVector = await embedText(text);
1204
+ const scored = [];
1205
+ for (const item of items) {
1206
+ if (item.invalidatedAt) continue;
1207
+ if (excludeId && item.id === excludeId) continue;
1208
+ try {
1209
+ const hash = contentHash(item.insight, "");
1210
+ let itemVector = getCachedInsightEmbedding(repoRoot, item.id, hash);
1211
+ if (!itemVector) {
1212
+ itemVector = await embedText(item.insight);
1213
+ setCachedInsightEmbedding(repoRoot, item.id, itemVector, hash);
1214
+ }
1215
+ const score = cosineSimilarity(queryVector, itemVector);
1216
+ if (score >= threshold) {
1217
+ scored.push({ item, score });
1218
+ }
1219
+ } catch {
1220
+ continue;
1221
+ }
1222
+ }
1223
+ scored.sort((a, b) => b.score - a.score);
1224
+ return scored;
1225
+ }
1226
+ var cctEmbeddingCache, DEFAULT_LIMIT, DEFAULT_THRESHOLD2;
1227
+ var init_vector = __esm({
1228
+ "src/memory/search/vector.ts"() {
1229
+ init_compound();
1230
+ init_embeddings();
1231
+ init_model();
1232
+ init_storage();
1233
+ cctEmbeddingCache = /* @__PURE__ */ new Map();
1234
+ DEFAULT_LIMIT = 10;
1235
+ DEFAULT_THRESHOLD2 = 0.8;
1236
+ }
1237
+ });
1238
+
1239
+ // src/utils.ts
1240
+ function getLessonAgeDays(lesson) {
1241
+ const created = new Date(lesson.created).getTime();
1242
+ const now = Date.now();
1243
+ return Math.floor((now - created) / MS_PER_DAY);
1244
+ }
1245
+ var MS_PER_DAY;
1246
+ var init_utils = __esm({
1247
+ "src/utils.ts"() {
1248
+ MS_PER_DAY = 24 * 60 * 60 * 1e3;
1249
+ }
1250
+ });
1251
+
1252
+ // src/memory/search/ranking.ts
1253
+ function severityBoost(item) {
1254
+ switch (item.severity) {
1255
+ case "high":
1256
+ return HIGH_SEVERITY_BOOST;
1257
+ case "medium":
1258
+ return MEDIUM_SEVERITY_BOOST;
1259
+ case "low":
1260
+ return LOW_SEVERITY_BOOST;
1261
+ default:
1262
+ return MEDIUM_SEVERITY_BOOST;
1263
+ }
1264
+ }
1265
+ function recencyBoost(item) {
1266
+ const ageDays = getLessonAgeDays(item);
1267
+ return ageDays <= RECENCY_THRESHOLD_DAYS ? RECENCY_BOOST : 1;
1268
+ }
1269
+ function confirmationBoost(item) {
1270
+ return item.confirmed ? CONFIRMATION_BOOST : 1;
1271
+ }
1272
+ function calculateScore(item, vectorSimilarity) {
1273
+ const boost = Math.min(
1274
+ severityBoost(item) * recencyBoost(item) * confirmationBoost(item),
1275
+ MAX_COMBINED_BOOST
1276
+ );
1277
+ return vectorSimilarity * boost;
1278
+ }
1279
+ function rankLessons(lessons) {
1280
+ return lessons.map((scored) => ({
1281
+ ...scored,
1282
+ finalScore: calculateScore(scored.lesson, scored.score)
1283
+ })).sort((a, b) => (b.finalScore ?? 0) - (a.finalScore ?? 0));
1284
+ }
1285
+ var RECENCY_THRESHOLD_DAYS, HIGH_SEVERITY_BOOST, MEDIUM_SEVERITY_BOOST, LOW_SEVERITY_BOOST, RECENCY_BOOST, CONFIRMATION_BOOST, MAX_COMBINED_BOOST;
1286
+ var init_ranking = __esm({
1287
+ "src/memory/search/ranking.ts"() {
1288
+ init_utils();
1289
+ RECENCY_THRESHOLD_DAYS = 30;
1290
+ HIGH_SEVERITY_BOOST = 1.5;
1291
+ MEDIUM_SEVERITY_BOOST = 1;
1292
+ LOW_SEVERITY_BOOST = 0.8;
1293
+ RECENCY_BOOST = 1.2;
1294
+ CONFIRMATION_BOOST = 1.3;
1295
+ MAX_COMBINED_BOOST = 1.8;
1296
+ }
1297
+ });
1298
+
1299
+ // src/memory/search/prewarm.ts
1300
+ var init_prewarm = __esm({
1301
+ "src/memory/search/prewarm.ts"() {
1302
+ init_model();
1303
+ init_embeddings();
1304
+ init_storage();
1305
+ }
1306
+ });
1307
+
1308
+ // src/memory/search/index.ts
1309
+ var init_search2 = __esm({
1310
+ "src/memory/search/index.ts"() {
1311
+ init_vector();
1312
+ init_ranking();
1313
+ init_prewarm();
1314
+ init_hybrid();
1315
+ }
1316
+ });
1317
+
1318
+ // src/memory/storage/sqlite-knowledge/schema.ts
1319
+ function createKnowledgeSchema(database) {
1320
+ database.exec(SCHEMA_SQL2);
1321
+ database.pragma(`user_version = ${KNOWLEDGE_SCHEMA_VERSION}`);
1322
+ }
1323
+ var KNOWLEDGE_SCHEMA_VERSION, SCHEMA_SQL2;
1324
+ var init_schema2 = __esm({
1325
+ "src/memory/storage/sqlite-knowledge/schema.ts"() {
1326
+ KNOWLEDGE_SCHEMA_VERSION = 2;
1327
+ SCHEMA_SQL2 = `
1328
+ CREATE TABLE IF NOT EXISTS chunks (
1329
+ id TEXT PRIMARY KEY,
1330
+ file_path TEXT NOT NULL,
1331
+ start_line INTEGER NOT NULL,
1332
+ end_line INTEGER NOT NULL,
1333
+ content_hash TEXT NOT NULL,
1334
+ text TEXT NOT NULL,
737
1335
  embedding BLOB,
738
- content_hash TEXT,
739
- invalidated_at TEXT,
740
- invalidation_reason TEXT,
741
- citation_file TEXT,
742
- citation_line INTEGER,
743
- citation_commit TEXT,
744
- compaction_level INTEGER DEFAULT 0,
745
- compacted_at TEXT,
746
- pattern_bad TEXT,
747
- pattern_good TEXT
1336
+ model TEXT,
1337
+ updated_at TEXT NOT NULL
748
1338
  );
749
1339
 
750
- CREATE VIRTUAL TABLE IF NOT EXISTS lessons_fts USING fts5(
751
- id, trigger, insight, tags, pattern_bad, pattern_good,
752
- content='lessons', content_rowid='rowid'
1340
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
1341
+ text,
1342
+ content='chunks', content_rowid='rowid'
753
1343
  );
754
1344
 
755
- CREATE TRIGGER IF NOT EXISTS lessons_ai AFTER INSERT ON lessons BEGIN
756
- INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
757
- VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
1345
+ CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
1346
+ INSERT INTO chunks_fts(rowid, text)
1347
+ VALUES (new.rowid, new.text);
758
1348
  END;
759
1349
 
760
- CREATE TRIGGER IF NOT EXISTS lessons_ad AFTER DELETE ON lessons BEGIN
761
- INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
762
- VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
1350
+ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
1351
+ INSERT INTO chunks_fts(chunks_fts, rowid, text)
1352
+ VALUES ('delete', old.rowid, old.text);
763
1353
  END;
764
1354
 
765
- CREATE TRIGGER IF NOT EXISTS lessons_au AFTER UPDATE ON lessons BEGIN
766
- INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
767
- VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
768
- INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
769
- VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
1355
+ CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
1356
+ INSERT INTO chunks_fts(chunks_fts, rowid, text)
1357
+ VALUES ('delete', old.rowid, old.text);
1358
+ INSERT INTO chunks_fts(rowid, text)
1359
+ VALUES (new.rowid, new.text);
770
1360
  END;
771
1361
 
772
- CREATE INDEX IF NOT EXISTS idx_lessons_created ON lessons(created);
773
- CREATE INDEX IF NOT EXISTS idx_lessons_confirmed ON lessons(confirmed);
774
- CREATE INDEX IF NOT EXISTS idx_lessons_severity ON lessons(severity);
775
- CREATE INDEX IF NOT EXISTS idx_lessons_type ON lessons(type);
1362
+ CREATE INDEX IF NOT EXISTS idx_chunks_file_path ON chunks(file_path);
776
1363
 
777
1364
  CREATE TABLE IF NOT EXISTS metadata (
778
1365
  key TEXT PRIMARY KEY,
779
1366
  value TEXT NOT NULL
780
1367
  );
781
1368
  `;
782
- function createSchema(database) {
783
- database.exec(SCHEMA_SQL);
784
- database.pragma(`user_version = ${SCHEMA_VERSION}`);
785
- }
786
-
787
- // src/memory/storage/sqlite/connection.ts
788
- var DB_PATH = ".claude/.cache/lessons.sqlite";
789
- var dbMap = /* @__PURE__ */ new Map();
790
- function hasExpectedVersion(database) {
791
- const row = database.pragma("user_version", { simple: true });
792
- return row === SCHEMA_VERSION;
793
- }
794
- function openDb(repoRoot, options = {}) {
1369
+ }
1370
+ });
1371
+ function openKnowledgeDb(repoRoot, options = {}) {
795
1372
  const { inMemory = false } = options;
796
- const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, DB_PATH);
797
- const cached = dbMap.get(key);
1373
+ const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, KNOWLEDGE_DB_PATH);
1374
+ const cached = knowledgeDbMap.get(key);
798
1375
  if (cached) {
799
1376
  return cached;
800
1377
  }
@@ -806,7 +1383,8 @@ function openDb(repoRoot, options = {}) {
806
1383
  const dir = dirname(key);
807
1384
  mkdirSync(dir, { recursive: true });
808
1385
  database = new Database(key);
809
- if (!hasExpectedVersion(database)) {
1386
+ const version = database.pragma("user_version", { simple: true });
1387
+ if (version !== 0 && version !== KNOWLEDGE_SCHEMA_VERSION) {
810
1388
  database.close();
811
1389
  try {
812
1390
  unlinkSync(key);
@@ -816,739 +1394,787 @@ function openDb(repoRoot, options = {}) {
816
1394
  }
817
1395
  database.pragma("journal_mode = WAL");
818
1396
  }
819
- createSchema(database);
820
- dbMap.set(key, database);
821
- return database;
822
- }
823
- function closeDb() {
824
- for (const database of dbMap.values()) {
825
- database.close();
826
- }
827
- dbMap.clear();
828
- }
829
- function contentHash(trigger, insight) {
830
- return createHash("sha256").update(`${trigger} ${insight}`).digest("hex");
831
- }
832
- function getCachedEmbedding(repoRoot, lessonId, expectedHash) {
833
- const database = openDb(repoRoot);
834
- const row = database.prepare("SELECT embedding, content_hash FROM lessons WHERE id = ?").get(lessonId);
835
- if (!row || !row.embedding || !row.content_hash) {
836
- return null;
837
- }
838
- if (expectedHash && row.content_hash !== expectedHash) {
839
- return null;
840
- }
841
- const float32 = new Float32Array(
842
- row.embedding.buffer,
843
- row.embedding.byteOffset,
844
- row.embedding.byteLength / 4
845
- );
846
- return Array.from(float32);
847
- }
848
- function setCachedEmbedding(repoRoot, lessonId, embedding, hash) {
849
- const database = openDb(repoRoot);
850
- const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
851
- const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
852
- database.prepare("UPDATE lessons SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, lessonId);
853
- }
854
- function collectCachedEmbeddings(database) {
855
- const cache = /* @__PURE__ */ new Map();
856
- const rows = database.prepare("SELECT id, embedding, content_hash FROM lessons WHERE embedding IS NOT NULL").all();
857
- for (const row of rows) {
858
- if (row.embedding && row.content_hash) {
859
- cache.set(row.id, { embedding: row.embedding, contentHash: row.content_hash });
860
- }
861
- }
862
- return cache;
863
- }
864
- var INSERT_LESSON_SQL = `
865
- INSERT INTO lessons (id, type, trigger, insight, evidence, severity, tags, source, context, supersedes, related, created, confirmed, deleted, retrieval_count, last_retrieved, embedding, content_hash, invalidated_at, invalidation_reason, citation_file, citation_line, citation_commit, compaction_level, compacted_at, pattern_bad, pattern_good)
866
- VALUES (@id, @type, @trigger, @insight, @evidence, @severity, @tags, @source, @context, @supersedes, @related, @created, @confirmed, @deleted, @retrieval_count, @last_retrieved, @embedding, @content_hash, @invalidated_at, @invalidation_reason, @citation_file, @citation_line, @citation_commit, @compaction_level, @compacted_at, @pattern_bad, @pattern_good)
867
- `;
868
- function getJsonlMtime(repoRoot) {
869
- const jsonlPath = join(repoRoot, LESSONS_PATH);
870
- try {
871
- const stat = statSync(jsonlPath);
872
- return stat.mtimeMs;
873
- } catch {
874
- return null;
875
- }
876
- }
877
- function getLastSyncMtime(database) {
878
- const row = database.prepare("SELECT value FROM metadata WHERE key = ?").get("last_sync_mtime");
879
- return row ? parseFloat(row.value) : null;
880
- }
881
- function setLastSyncMtime(database, mtime) {
882
- database.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run("last_sync_mtime", mtime.toString());
883
- }
884
- async function rebuildIndex(repoRoot) {
885
- const database = openDb(repoRoot);
886
- const { items } = await readMemoryItems(repoRoot);
887
- const cachedEmbeddings = collectCachedEmbeddings(database);
888
- database.exec("DELETE FROM lessons");
889
- if (items.length === 0) {
890
- const mtime2 = getJsonlMtime(repoRoot);
891
- if (mtime2 !== null) {
892
- setLastSyncMtime(database, mtime2);
893
- }
894
- return;
895
- }
896
- const insert = database.prepare(INSERT_LESSON_SQL);
897
- const insertMany = database.transaction((memoryItems) => {
898
- for (const item of memoryItems) {
899
- const newHash = contentHash(item.trigger, item.insight);
900
- const cached = cachedEmbeddings.get(item.id);
901
- const hasValidCache = cached && cached.contentHash === newHash;
902
- insert.run({
903
- id: item.id,
904
- type: item.type,
905
- trigger: item.trigger,
906
- insight: item.insight,
907
- evidence: item.evidence ?? null,
908
- severity: item.severity ?? null,
909
- tags: item.tags.join(","),
910
- source: item.source,
911
- context: JSON.stringify(item.context),
912
- supersedes: JSON.stringify(item.supersedes),
913
- related: JSON.stringify(item.related),
914
- created: item.created,
915
- confirmed: item.confirmed ? 1 : 0,
916
- deleted: item.deleted ? 1 : 0,
917
- retrieval_count: item.retrievalCount ?? 0,
918
- last_retrieved: item.lastRetrieved ?? null,
919
- embedding: hasValidCache ? cached.embedding : null,
920
- content_hash: hasValidCache ? cached.contentHash : null,
921
- invalidated_at: item.invalidatedAt ?? null,
922
- invalidation_reason: item.invalidationReason ?? null,
923
- citation_file: item.citation?.file ?? null,
924
- citation_line: item.citation?.line ?? null,
925
- citation_commit: item.citation?.commit ?? null,
926
- compaction_level: item.compactionLevel ?? 0,
927
- compacted_at: item.compactedAt ?? null,
928
- pattern_bad: item.pattern?.bad ?? null,
929
- pattern_good: item.pattern?.good ?? null
930
- });
931
- }
932
- });
933
- insertMany(items);
934
- const mtime = getJsonlMtime(repoRoot);
935
- if (mtime !== null) {
936
- setLastSyncMtime(database, mtime);
937
- }
1397
+ createKnowledgeSchema(database);
1398
+ knowledgeDbMap.set(key, database);
1399
+ return database;
938
1400
  }
939
- async function syncIfNeeded(repoRoot, options = {}) {
940
- const { force = false } = options;
941
- const jsonlMtime = getJsonlMtime(repoRoot);
942
- if (jsonlMtime === null && !force) {
943
- return false;
1401
+ function closeKnowledgeDb() {
1402
+ for (const database of knowledgeDbMap.values()) {
1403
+ database.close();
944
1404
  }
945
- const database = openDb(repoRoot);
946
- const lastSyncMtime = getLastSyncMtime(database);
947
- const needsRebuild = force || lastSyncMtime === null || jsonlMtime !== null && jsonlMtime > lastSyncMtime;
948
- if (needsRebuild) {
949
- await rebuildIndex(repoRoot);
950
- return true;
1405
+ knowledgeDbMap.clear();
1406
+ }
1407
+ var KNOWLEDGE_DB_PATH, knowledgeDbMap;
1408
+ var init_connection2 = __esm({
1409
+ "src/memory/storage/sqlite-knowledge/connection.ts"() {
1410
+ init_availability();
1411
+ init_schema2();
1412
+ KNOWLEDGE_DB_PATH = ".claude/.cache/knowledge.sqlite";
1413
+ knowledgeDbMap = /* @__PURE__ */ new Map();
951
1414
  }
952
- return false;
1415
+ });
1416
+ function generateChunkId(filePath, startLine, endLine) {
1417
+ return createHash("sha256").update(`${filePath}:${startLine}:${endLine}`).digest("hex").slice(0, 16);
953
1418
  }
954
-
955
- // src/memory/storage/sqlite/index.ts
956
- init_availability();
957
-
958
- // src/memory/search/hybrid.ts
959
- var DEFAULT_VECTOR_WEIGHT = 0.7;
960
- var DEFAULT_TEXT_WEIGHT = 0.3;
961
- var CANDIDATE_MULTIPLIER = 4;
962
- var MIN_HYBRID_SCORE = 0.35;
963
- function normalizeBm25Rank(rank) {
964
- if (!Number.isFinite(rank)) return 0;
965
- const abs = Math.abs(rank);
966
- return abs / (1 + abs);
1419
+ function chunkContentHash(text) {
1420
+ return createHash("sha256").update(text).digest("hex");
967
1421
  }
968
- function mergeHybridScores(vectorResults, keywordResults, getId, options) {
969
- if (vectorResults.length === 0 && keywordResults.length === 0) return [];
970
- const rawVecW = options?.vectorWeight ?? DEFAULT_VECTOR_WEIGHT;
971
- const rawTxtW = options?.textWeight ?? DEFAULT_TEXT_WEIGHT;
972
- const total = rawVecW + rawTxtW;
973
- if (total <= 0) return [];
974
- const vecW = rawVecW / total;
975
- const txtW = rawTxtW / total;
976
- const limit = options?.limit;
977
- const minScore = options?.minScore;
978
- const merged = /* @__PURE__ */ new Map();
979
- for (const v of vectorResults) {
980
- merged.set(getId(v.item), { item: v.item, vecScore: v.score, txtScore: 0 });
1422
+ var SUPPORTED_EXTENSIONS, CODE_EXTENSIONS;
1423
+ var init_types3 = __esm({
1424
+ "src/memory/knowledge/types.ts"() {
1425
+ SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
1426
+ ".md",
1427
+ ".txt",
1428
+ ".rst",
1429
+ ".ts",
1430
+ ".py",
1431
+ ".js",
1432
+ ".tsx",
1433
+ ".jsx"
1434
+ ]);
1435
+ CODE_EXTENSIONS = /* @__PURE__ */ new Set([
1436
+ ".ts",
1437
+ ".tsx",
1438
+ ".js",
1439
+ ".jsx",
1440
+ ".py"
1441
+ ]);
981
1442
  }
982
- for (const k of keywordResults) {
983
- const id = getId(k.item);
984
- const existing = merged.get(id);
985
- if (existing) {
986
- existing.txtScore = k.score;
987
- } else {
988
- merged.set(id, { item: k.item, vecScore: 0, txtScore: k.score });
989
- }
1443
+ });
1444
+
1445
+ // src/memory/storage/sqlite-knowledge/cache.ts
1446
+ function getCachedChunkEmbedding(repoRoot, chunkId, expectedHash) {
1447
+ const database = openKnowledgeDb(repoRoot);
1448
+ const row = database.prepare("SELECT embedding, content_hash FROM chunks WHERE id = ?").get(chunkId);
1449
+ if (!row || !row.embedding || !row.content_hash) {
1450
+ return null;
990
1451
  }
991
- const results = [];
992
- for (const entry of merged.values()) {
993
- results.push({
994
- item: entry.item,
995
- score: vecW * entry.vecScore + txtW * entry.txtScore
996
- });
1452
+ if (expectedHash && row.content_hash !== expectedHash) {
1453
+ return null;
997
1454
  }
998
- results.sort((a, b) => b.score - a.score);
999
- const filtered = minScore !== void 0 ? results.filter((r) => r.score >= minScore) : results;
1000
- return limit !== void 0 ? filtered.slice(0, limit) : filtered;
1455
+ return new Float32Array(
1456
+ row.embedding.buffer,
1457
+ row.embedding.byteOffset,
1458
+ row.embedding.byteLength / 4
1459
+ );
1001
1460
  }
1002
- function mergeHybridResults(vectorResults, keywordResults, options) {
1003
- const genericVec = vectorResults.map((v) => ({ item: v.lesson, score: v.score }));
1004
- const genericKw = keywordResults.map((k) => ({ item: k.lesson, score: k.score }));
1005
- const merged = mergeHybridScores(genericVec, genericKw, (item) => item.id, options);
1006
- return merged.map((m) => ({ lesson: m.item, score: m.score }));
1461
+ function setCachedChunkEmbedding(repoRoot, chunkId, embedding, hash) {
1462
+ const database = openKnowledgeDb(repoRoot);
1463
+ const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
1464
+ const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
1465
+ database.prepare("UPDATE chunks SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, chunkId);
1007
1466
  }
1008
-
1009
- // src/memory/storage/sqlite/search.ts
1010
- function safeJsonParse(value, fallback) {
1011
- try {
1012
- return JSON.parse(value);
1013
- } catch {
1014
- return fallback;
1467
+ function collectCachedChunkEmbeddings(database) {
1468
+ const cache = /* @__PURE__ */ new Map();
1469
+ const rows = database.prepare("SELECT id, embedding, content_hash FROM chunks WHERE embedding IS NOT NULL").all();
1470
+ for (const row of rows) {
1471
+ if (row.embedding && row.content_hash) {
1472
+ cache.set(row.id, { embedding: row.embedding, contentHash: row.content_hash });
1473
+ }
1015
1474
  }
1475
+ return cache;
1016
1476
  }
1017
- function rowToMemoryItem(row) {
1018
- const item = {
1477
+ var init_cache2 = __esm({
1478
+ "src/memory/storage/sqlite-knowledge/cache.ts"() {
1479
+ init_connection2();
1480
+ init_types3();
1481
+ }
1482
+ });
1483
+
1484
+ // src/memory/storage/sqlite-knowledge/search.ts
1485
+ function rowToChunk(row) {
1486
+ const chunk = {
1019
1487
  id: row.id,
1020
- type: row.type,
1021
- trigger: row.trigger,
1022
- insight: row.insight,
1023
- tags: row.tags ? row.tags.split(",").filter(Boolean) : [],
1024
- source: row.source,
1025
- context: safeJsonParse(row.context, {}),
1026
- supersedes: safeJsonParse(row.supersedes, []),
1027
- related: safeJsonParse(row.related, []),
1028
- created: row.created,
1029
- confirmed: row.confirmed === 1
1488
+ filePath: row.file_path,
1489
+ startLine: row.start_line,
1490
+ endLine: row.end_line,
1491
+ contentHash: row.content_hash,
1492
+ text: row.text,
1493
+ updatedAt: row.updated_at
1030
1494
  };
1031
- if (row.evidence !== null) item.evidence = row.evidence;
1032
- if (row.severity !== null) item.severity = row.severity;
1033
- if (row.deleted === 1) item.deleted = true;
1034
- if (row.retrieval_count > 0) item.retrievalCount = row.retrieval_count;
1035
- if (row.invalidated_at !== null) item.invalidatedAt = row.invalidated_at;
1036
- if (row.invalidation_reason !== null) item.invalidationReason = row.invalidation_reason;
1037
- if (row.citation_file !== null) {
1038
- item.citation = {
1039
- file: row.citation_file,
1040
- ...row.citation_line !== null && { line: row.citation_line },
1041
- ...row.citation_commit !== null && { commit: row.citation_commit }
1042
- };
1043
- }
1044
- if (row.compaction_level !== null && row.compaction_level !== 0) {
1045
- item.compactionLevel = row.compaction_level;
1046
- }
1047
- if (row.compacted_at !== null) item.compactedAt = row.compacted_at;
1048
- if (row.last_retrieved !== null) item.lastRetrieved = row.last_retrieved;
1049
- if (row.pattern_bad !== null && row.pattern_good !== null) {
1050
- item.pattern = { bad: row.pattern_bad, good: row.pattern_good };
1495
+ if (row.model !== null) {
1496
+ chunk.model = row.model;
1051
1497
  }
1052
- const result = MemoryItemSchema.safeParse(item);
1053
- if (!result.success) return null;
1054
- return result.data;
1498
+ return chunk;
1055
1499
  }
1056
- var FTS_OPERATORS = /* @__PURE__ */ new Set(["AND", "OR", "NOT", "NEAR"]);
1057
- function sanitizeFtsQuery(query) {
1058
- const stripped = query.replace(/["*^+\-():{}]/g, "");
1059
- const tokens = stripped.split(/\s+/).filter((t) => t.length > 0 && !FTS_OPERATORS.has(t));
1060
- return tokens.join(" ");
1500
+ function searchChunksKeywordScored(repoRoot, query, limit) {
1501
+ const database = openKnowledgeDb(repoRoot);
1502
+ const sanitized = sanitizeFtsQuery(query);
1503
+ if (sanitized === "") return [];
1504
+ try {
1505
+ const rows = database.prepare(
1506
+ `SELECT c.*, fts.rank
1507
+ FROM chunks c
1508
+ JOIN chunks_fts fts ON c.rowid = fts.rowid
1509
+ WHERE chunks_fts MATCH ?
1510
+ ORDER BY fts.rank
1511
+ LIMIT ?`
1512
+ ).all(sanitized, limit);
1513
+ return rows.map((row) => ({
1514
+ chunk: rowToChunk(row),
1515
+ score: normalizeBm25Rank(row.rank)
1516
+ }));
1517
+ } catch (err) {
1518
+ const message = err instanceof Error ? err.message : "Unknown FTS5 error";
1519
+ console.error(`[compound-agent] knowledge scored search error: ${message}`);
1520
+ return [];
1521
+ }
1061
1522
  }
1062
- function incrementRetrievalCount(repoRoot, lessonIds) {
1063
- if (lessonIds.length === 0) return;
1064
- const database = openDb(repoRoot);
1065
- const now = (/* @__PURE__ */ new Date()).toISOString();
1066
- const update = database.prepare(`
1067
- UPDATE lessons
1068
- SET retrieval_count = retrieval_count + 1,
1069
- last_retrieved = ?
1070
- WHERE id = ?
1523
+ var init_search3 = __esm({
1524
+ "src/memory/storage/sqlite-knowledge/search.ts"() {
1525
+ init_connection2();
1526
+ init_search();
1527
+ init_hybrid();
1528
+ }
1529
+ });
1530
+
1531
+ // src/memory/storage/sqlite-knowledge/sync.ts
1532
+ function upsertChunks(repoRoot, chunks, embeddings) {
1533
+ if (chunks.length === 0) return;
1534
+ const database = openKnowledgeDb(repoRoot);
1535
+ const insert = database.prepare(`
1536
+ INSERT OR REPLACE INTO chunks (id, file_path, start_line, end_line, content_hash, text, embedding, model, updated_at)
1537
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1071
1538
  `);
1072
- const updateMany = database.transaction((ids) => {
1073
- for (const id of ids) {
1074
- update.run(now, id);
1539
+ const upsertMany = database.transaction((items) => {
1540
+ for (const chunk of items) {
1541
+ const emb = embeddings?.get(chunk.id);
1542
+ const embBuffer = emb ? Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength) : null;
1543
+ insert.run(
1544
+ chunk.id,
1545
+ chunk.filePath,
1546
+ chunk.startLine,
1547
+ chunk.endLine,
1548
+ chunk.contentHash,
1549
+ chunk.text,
1550
+ embBuffer,
1551
+ chunk.model ?? null,
1552
+ chunk.updatedAt
1553
+ );
1075
1554
  }
1076
1555
  });
1077
- updateMany(lessonIds);
1556
+ upsertMany(chunks);
1557
+ }
1558
+ function deleteChunksByFilePath(repoRoot, filePaths) {
1559
+ if (filePaths.length === 0) return;
1560
+ const database = openKnowledgeDb(repoRoot);
1561
+ const del = database.prepare("DELETE FROM chunks WHERE file_path = ?");
1562
+ const deleteMany = database.transaction((paths) => {
1563
+ for (const path of paths) {
1564
+ del.run(path);
1565
+ }
1566
+ });
1567
+ deleteMany(filePaths);
1568
+ }
1569
+ function getIndexedFilePaths(repoRoot) {
1570
+ const database = openKnowledgeDb(repoRoot);
1571
+ const rows = database.prepare("SELECT DISTINCT file_path FROM chunks").all();
1572
+ return rows.map((r) => r.file_path);
1573
+ }
1574
+ function getLastIndexTime(repoRoot) {
1575
+ const database = openKnowledgeDb(repoRoot);
1576
+ const row = database.prepare("SELECT value FROM metadata WHERE key = 'last_index_time'").get();
1577
+ return row?.value ?? null;
1578
+ }
1579
+ function getChunkCount(repoRoot) {
1580
+ const database = openKnowledgeDb(repoRoot);
1581
+ const row = database.prepare("SELECT COUNT(*) as cnt FROM chunks").get();
1582
+ return row.cnt;
1078
1583
  }
1079
- function executeFtsQuery(repoRoot, query, limit, options) {
1080
- const database = openDb(repoRoot);
1081
- const sanitized = sanitizeFtsQuery(query);
1082
- if (sanitized === "") return [];
1083
- const selectCols = options.includeRank ? "l.*, fts.rank" : "l.*";
1084
- const orderClause = options.includeRank ? "ORDER BY fts.rank" : "";
1085
- const typeClause = options.typeFilter ? "AND l.type = ?" : "";
1086
- const sql = `
1087
- SELECT ${selectCols}
1088
- FROM lessons l
1089
- JOIN lessons_fts fts ON l.rowid = fts.rowid
1090
- WHERE lessons_fts MATCH ?
1091
- AND l.invalidated_at IS NULL
1092
- ${typeClause}
1093
- ${orderClause}
1094
- LIMIT ?
1095
- `;
1096
- const params = options.typeFilter ? [sanitized, options.typeFilter, limit] : [sanitized, limit];
1097
- try {
1098
- return database.prepare(sql).all(...params);
1099
- } catch (err) {
1100
- const message = err instanceof Error ? err.message : "Unknown FTS5 error";
1101
- console.error(`[compound-agent] search error: ${message}`);
1102
- return [];
1103
- }
1584
+ function getChunkCountByFilePath(repoRoot, filePath) {
1585
+ const database = openKnowledgeDb(repoRoot);
1586
+ const row = database.prepare("SELECT COUNT(*) as cnt FROM chunks WHERE file_path = ?").get(filePath);
1587
+ return row.cnt;
1104
1588
  }
1105
- async function searchKeyword(repoRoot, query, limit, typeFilter) {
1106
- const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: false, typeFilter });
1107
- return rows.map(rowToMemoryItem).filter((x) => x !== null);
1589
+ function setLastIndexTime(repoRoot, time) {
1590
+ const database = openKnowledgeDb(repoRoot);
1591
+ database.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES ('last_index_time', ?)").run(time);
1108
1592
  }
1109
- async function searchKeywordScored(repoRoot, query, limit, typeFilter) {
1110
- const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: true, typeFilter });
1111
- const results = [];
1112
- for (const row of rows) {
1113
- const lesson = rowToMemoryItem(row);
1114
- if (lesson) {
1115
- results.push({ lesson, score: normalizeBm25Rank(row.rank) });
1116
- }
1593
+ var init_sync2 = __esm({
1594
+ "src/memory/storage/sqlite-knowledge/sync.ts"() {
1595
+ init_connection2();
1117
1596
  }
1118
- return results;
1119
- }
1597
+ });
1120
1598
 
1121
- // src/utils.ts
1122
- var MS_PER_DAY = 24 * 60 * 60 * 1e3;
1123
- function getLessonAgeDays(lesson) {
1124
- const created = new Date(lesson.created).getTime();
1125
- const now = Date.now();
1126
- return Math.floor((now - created) / MS_PER_DAY);
1127
- }
1128
- var MODEL_URI = "hf:ggml-org/embeddinggemma-300M-qat-q4_0-GGUF/embeddinggemma-300M-qat-Q4_0.gguf";
1129
- var MODEL_FILENAME = "hf_ggml-org_embeddinggemma-300M-qat-Q4_0.gguf";
1130
- var DEFAULT_MODEL_DIR = join(homedir(), ".node-llama-cpp", "models");
1131
- var cachedUsability = null;
1132
- function isModelAvailable() {
1133
- return existsSync(join(DEFAULT_MODEL_DIR, MODEL_FILENAME));
1599
+ // src/memory/storage/sqlite-knowledge/index.ts
1600
+ var sqlite_knowledge_exports = {};
1601
+ __export(sqlite_knowledge_exports, {
1602
+ KNOWLEDGE_DB_PATH: () => KNOWLEDGE_DB_PATH,
1603
+ KNOWLEDGE_SCHEMA_VERSION: () => KNOWLEDGE_SCHEMA_VERSION,
1604
+ chunkContentHash: () => chunkContentHash,
1605
+ closeKnowledgeDb: () => closeKnowledgeDb,
1606
+ collectCachedChunkEmbeddings: () => collectCachedChunkEmbeddings,
1607
+ deleteChunksByFilePath: () => deleteChunksByFilePath,
1608
+ getCachedChunkEmbedding: () => getCachedChunkEmbedding,
1609
+ getChunkCount: () => getChunkCount,
1610
+ getChunkCountByFilePath: () => getChunkCountByFilePath,
1611
+ getIndexedFilePaths: () => getIndexedFilePaths,
1612
+ getLastIndexTime: () => getLastIndexTime,
1613
+ openKnowledgeDb: () => openKnowledgeDb,
1614
+ searchChunksKeywordScored: () => searchChunksKeywordScored,
1615
+ setCachedChunkEmbedding: () => setCachedChunkEmbedding,
1616
+ setLastIndexTime: () => setLastIndexTime,
1617
+ upsertChunks: () => upsertChunks
1618
+ });
1619
+ var init_sqlite_knowledge = __esm({
1620
+ "src/memory/storage/sqlite-knowledge/index.ts"() {
1621
+ init_connection2();
1622
+ init_schema2();
1623
+ init_cache2();
1624
+ init_search3();
1625
+ init_sync2();
1626
+ }
1627
+ });
1628
+ function isBinary(content) {
1629
+ return content.includes("\0");
1134
1630
  }
1135
- async function isModelUsable() {
1136
- if (cachedUsability !== null) {
1137
- return cachedUsability;
1631
+ function splitIntoSections(fileLines, ext) {
1632
+ if (ext === ".md") {
1633
+ return splitMarkdown(fileLines);
1138
1634
  }
1139
- if (!isModelAvailable()) {
1140
- cachedUsability = {
1141
- usable: false,
1142
- reason: "Embedding model file not found",
1143
- action: "Run: npx ca download-model"
1144
- };
1145
- return cachedUsability;
1635
+ if (ext === ".rst") {
1636
+ return splitParagraphs(fileLines);
1146
1637
  }
1147
- let llama = null;
1148
- let model = null;
1149
- let context = null;
1150
- try {
1151
- const modelPath = join(DEFAULT_MODEL_DIR, MODEL_FILENAME);
1152
- llama = await getLlama({
1153
- build: "never",
1154
- // Never compile from source in a deployed tool
1155
- progressLogs: false,
1156
- // Suppress prebuilt binary fallback warnings
1157
- logLevel: LlamaLogLevel.error
1158
- // Only surface real errors from C++ backend
1159
- // Set NODE_LLAMA_CPP_DEBUG=true to re-enable all output for troubleshooting
1160
- });
1161
- model = await llama.loadModel({ modelPath });
1162
- context = await model.createEmbeddingContext();
1163
- cachedUsability = { usable: true };
1164
- return cachedUsability;
1165
- } catch (err) {
1166
- const message = err instanceof Error ? err.message : "Unknown error";
1167
- cachedUsability = {
1168
- usable: false,
1169
- reason: `Embedding model runtime initialization failed: ${message}`,
1170
- action: "Check system compatibility or reinstall: npx ca download-model"
1171
- };
1172
- return cachedUsability;
1173
- } finally {
1174
- if (context) {
1175
- try {
1176
- await context.dispose();
1177
- } catch {
1178
- }
1638
+ if (CODE_EXTENSIONS.has(ext)) {
1639
+ return splitCode(fileLines);
1640
+ }
1641
+ return splitParagraphs(fileLines);
1642
+ }
1643
+ function splitMarkdown(fileLines) {
1644
+ const sections = [];
1645
+ let current = [];
1646
+ let inCodeBlock = false;
1647
+ for (let i = 0; i < fileLines.length; i++) {
1648
+ const line = fileLines[i];
1649
+ const lineObj = { lineNumber: i + 1, text: line };
1650
+ if (line.trimStart().startsWith("```")) {
1651
+ inCodeBlock = !inCodeBlock;
1652
+ current.push(lineObj);
1653
+ continue;
1179
1654
  }
1180
- if (model) {
1181
- try {
1182
- await model.dispose();
1183
- } catch {
1184
- }
1655
+ if (!inCodeBlock && /^#{2,}\s/.test(line) && current.length > 0) {
1656
+ sections.push(current);
1657
+ current = [lineObj];
1658
+ continue;
1185
1659
  }
1186
- if (llama) {
1187
- try {
1188
- await llama.dispose();
1189
- } catch {
1190
- }
1660
+ if (!inCodeBlock && line.trim() === "" && current.length > 0 && current.some((l) => l.text.trim() !== "")) {
1661
+ current.push(lineObj);
1662
+ sections.push(current);
1663
+ current = [];
1664
+ continue;
1191
1665
  }
1666
+ current.push(lineObj);
1667
+ }
1668
+ if (current.length > 0) {
1669
+ sections.push(current);
1192
1670
  }
1671
+ return sections;
1193
1672
  }
1194
- async function resolveModel(options = {}) {
1195
- const { cli = true } = options;
1196
- return resolveModelFile(MODEL_URI, { cli });
1673
+ function splitCode(fileLines) {
1674
+ const sections = [];
1675
+ let current = [];
1676
+ for (let i = 0; i < fileLines.length; i++) {
1677
+ const line = fileLines[i];
1678
+ const lineObj = { lineNumber: i + 1, text: line };
1679
+ if (line.trim() === "" && current.length > 0) {
1680
+ let hasNextNonBlank = false;
1681
+ for (let j = i + 1; j < fileLines.length; j++) {
1682
+ if (fileLines[j].trim() !== "") {
1683
+ hasNextNonBlank = true;
1684
+ break;
1685
+ }
1686
+ }
1687
+ if (hasNextNonBlank) {
1688
+ sections.push(current);
1689
+ current = [lineObj];
1690
+ continue;
1691
+ }
1692
+ }
1693
+ current.push(lineObj);
1694
+ }
1695
+ if (current.length > 0) {
1696
+ sections.push(current);
1697
+ }
1698
+ return sections;
1197
1699
  }
1198
-
1199
- // src/memory/embeddings/nomic.ts
1200
- var embeddingContext = null;
1201
- var pendingInit = null;
1202
- var llamaInstance = null;
1203
- var modelInstance = null;
1204
- async function getEmbedding() {
1205
- if (embeddingContext) return embeddingContext;
1206
- if (pendingInit) return pendingInit;
1207
- pendingInit = (async () => {
1208
- try {
1209
- const modelPath = await resolveModel({ cli: true });
1210
- llamaInstance = await getLlama({
1211
- build: "never",
1212
- // Never compile from source in a deployed tool
1213
- progressLogs: false,
1214
- // Suppress prebuilt binary fallback warnings
1215
- logLevel: LlamaLogLevel.error
1216
- // Only surface real errors from C++ backend
1217
- // Set NODE_LLAMA_CPP_DEBUG=true to re-enable all output for troubleshooting
1218
- });
1219
- modelInstance = await llamaInstance.loadModel({ modelPath });
1220
- embeddingContext = await modelInstance.createEmbeddingContext();
1221
- return embeddingContext;
1222
- } catch (err) {
1223
- pendingInit = null;
1224
- throw err;
1700
+ function splitParagraphs(fileLines) {
1701
+ const sections = [];
1702
+ let current = [];
1703
+ for (let i = 0; i < fileLines.length; i++) {
1704
+ const line = fileLines[i];
1705
+ const lineObj = { lineNumber: i + 1, text: line };
1706
+ if (line.trim() === "" && current.length > 0) {
1707
+ sections.push(current);
1708
+ current = [lineObj];
1709
+ continue;
1225
1710
  }
1226
- })();
1227
- return pendingInit;
1711
+ current.push(lineObj);
1712
+ }
1713
+ if (current.length > 0) {
1714
+ sections.push(current);
1715
+ }
1716
+ return sections;
1228
1717
  }
1229
- function unloadEmbedding() {
1230
- if (embeddingContext) {
1231
- embeddingContext.dispose().catch(() => {
1718
+ function sectionText(section) {
1719
+ return section.map((l) => l.text).join("\n");
1720
+ }
1721
+ function chunkFile(filePath, content, options) {
1722
+ if (content.trim() === "") return [];
1723
+ if (isBinary(content)) return [];
1724
+ const targetSize = options?.targetSize ?? DEFAULT_TARGET_SIZE;
1725
+ const overlapSize = options?.overlapSize ?? DEFAULT_OVERLAP_SIZE;
1726
+ const fileLines = content.split("\n");
1727
+ const ext = extname(filePath).toLowerCase();
1728
+ const sections = splitIntoSections(fileLines, ext);
1729
+ const chunks = [];
1730
+ let accumulated = [];
1731
+ let accumulatedLength = 0;
1732
+ function emitChunk(lines, overlapLines2) {
1733
+ if (lines.length === 0) return [];
1734
+ const allLines = [...overlapLines2, ...lines];
1735
+ const text = allLines.map((l) => l.text).join("\n");
1736
+ const startLine = allLines[0].lineNumber;
1737
+ const endLine = allLines[allLines.length - 1].lineNumber;
1738
+ chunks.push({
1739
+ id: generateChunkId(filePath, startLine, endLine),
1740
+ filePath,
1741
+ startLine,
1742
+ endLine,
1743
+ text,
1744
+ contentHash: chunkContentHash(text)
1232
1745
  });
1233
- embeddingContext = null;
1746
+ if (overlapSize <= 0) return [];
1747
+ const overlapResult = [];
1748
+ let overlapLen = 0;
1749
+ for (let i = lines.length - 1; i >= 0; i--) {
1750
+ const lineLen = lines[i].text.length + 1;
1751
+ if (overlapLen + lineLen > overlapSize && overlapResult.length > 0) break;
1752
+ overlapResult.unshift(lines[i]);
1753
+ overlapLen += lineLen;
1754
+ }
1755
+ return overlapResult;
1234
1756
  }
1235
- if (modelInstance) {
1236
- modelInstance.dispose().catch(() => {
1237
- });
1238
- modelInstance = null;
1757
+ let overlapLines = [];
1758
+ for (const section of sections) {
1759
+ const sectionLen = sectionText(section).length;
1760
+ if (accumulatedLength > 0 && accumulatedLength + sectionLen > targetSize) {
1761
+ overlapLines = emitChunk(accumulated, overlapLines);
1762
+ accumulated = [];
1763
+ accumulatedLength = 0;
1764
+ }
1765
+ accumulated.push(...section);
1766
+ accumulatedLength += sectionLen;
1767
+ if (accumulatedLength > targetSize) {
1768
+ overlapLines = emitChunk(accumulated, overlapLines);
1769
+ accumulated = [];
1770
+ accumulatedLength = 0;
1771
+ }
1239
1772
  }
1240
- if (llamaInstance) {
1241
- llamaInstance.dispose().catch(() => {
1242
- });
1243
- llamaInstance = null;
1773
+ if (accumulated.length > 0) {
1774
+ emitChunk(accumulated, overlapLines);
1244
1775
  }
1245
- pendingInit = null;
1246
- }
1247
- async function embedText(text) {
1248
- const ctx = await getEmbedding();
1249
- const result = await ctx.getEmbeddingFor(text);
1250
- return Array.from(result.vector);
1776
+ return chunks;
1251
1777
  }
1252
- async function embedTexts(texts) {
1253
- if (texts.length === 0) return [];
1254
- const ctx = await getEmbedding();
1255
- const results = [];
1256
- for (const text of texts) {
1257
- const result = await ctx.getEmbeddingFor(text);
1258
- results.push(Array.from(result.vector));
1778
+ var DEFAULT_TARGET_SIZE, DEFAULT_OVERLAP_SIZE;
1779
+ var init_chunking = __esm({
1780
+ "src/memory/knowledge/chunking.ts"() {
1781
+ init_types3();
1782
+ DEFAULT_TARGET_SIZE = 1600;
1783
+ DEFAULT_OVERLAP_SIZE = 320;
1259
1784
  }
1260
- return results;
1261
- }
1785
+ });
1262
1786
 
1263
- // src/compound/clustering.ts
1264
- var DEFAULT_THRESHOLD = 0.75;
1265
- function buildSimilarityMatrix(embeddings) {
1266
- const n = embeddings.length;
1267
- const matrix = Array.from({ length: n }, () => new Array(n).fill(0));
1268
- for (let i = 0; i < n; i++) {
1269
- matrix[i][i] = 1;
1270
- for (let j = i + 1; j < n; j++) {
1271
- const sim = cosineSimilarity(embeddings[i], embeddings[j]);
1272
- matrix[i][j] = sim;
1273
- matrix[j][i] = sim;
1274
- }
1275
- }
1276
- return matrix;
1787
+ // src/memory/knowledge/embed-chunks.ts
1788
+ var embed_chunks_exports = {};
1789
+ __export(embed_chunks_exports, {
1790
+ embedChunks: () => embedChunks,
1791
+ getUnembeddedChunkCount: () => getUnembeddedChunkCount
1792
+ });
1793
+ function getUnembeddedChunkCount(repoRoot) {
1794
+ const db = openKnowledgeDb(repoRoot);
1795
+ const row = db.prepare("SELECT COUNT(*) as count FROM chunks WHERE embedding IS NULL").get();
1796
+ return row.count;
1277
1797
  }
1278
- function clusterBySimilarity(items, embeddings, threshold = DEFAULT_THRESHOLD) {
1279
- const n = items.length;
1280
- if (n === 0) return { clusters: [], noise: [] };
1281
- const matrix = buildSimilarityMatrix(embeddings);
1282
- const parent = Array.from({ length: n }, (_, i) => i);
1283
- function find(x) {
1284
- while (parent[x] !== x) {
1285
- parent[x] = parent[parent[x]];
1286
- x = parent[x];
1287
- }
1288
- return x;
1289
- }
1290
- function union(a, b) {
1291
- const rootA = find(a);
1292
- const rootB = find(b);
1293
- if (rootA !== rootB) parent[rootA] = rootB;
1294
- }
1295
- for (let i = 0; i < n; i++) {
1296
- for (let j = i + 1; j < n; j++) {
1297
- if (matrix[i][j] >= threshold) {
1298
- union(i, j);
1299
- }
1798
+ async function embedChunks(repoRoot, options) {
1799
+ const start = Date.now();
1800
+ const onlyMissing = options?.onlyMissing ?? true;
1801
+ const db = openKnowledgeDb(repoRoot);
1802
+ const query = onlyMissing ? "SELECT id, text, content_hash FROM chunks WHERE embedding IS NULL" : "SELECT id, text, content_hash FROM chunks";
1803
+ const rows = db.prepare(query).all();
1804
+ const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks").get();
1805
+ const chunksSkipped = totalRow.count - rows.length;
1806
+ let chunksEmbedded = 0;
1807
+ const updateStmt = db.prepare(
1808
+ "UPDATE chunks SET embedding = ?, content_hash = ? WHERE id = ?"
1809
+ );
1810
+ const writeBatch = db.transaction((batch) => {
1811
+ for (const item of batch) {
1812
+ const buffer = Buffer.from(item.vector.buffer, item.vector.byteOffset, item.vector.byteLength);
1813
+ updateStmt.run(buffer, item.content_hash, item.id);
1300
1814
  }
1301
- }
1302
- const groups = /* @__PURE__ */ new Map();
1303
- for (let i = 0; i < n; i++) {
1304
- const root = find(i);
1305
- let group = groups.get(root);
1306
- if (!group) {
1307
- group = [];
1308
- groups.set(root, group);
1815
+ });
1816
+ for (let i = 0; i < rows.length; i += BATCH_SIZE) {
1817
+ const batch = rows.slice(i, i + BATCH_SIZE);
1818
+ const texts = batch.map((r) => r.text);
1819
+ const vectors = await embedTexts(texts);
1820
+ if (vectors.length !== texts.length) {
1821
+ throw new Error(`embedTexts returned ${vectors.length} vectors for ${texts.length} inputs`);
1309
1822
  }
1310
- group.push(items[i]);
1823
+ const enriched = batch.map((r, j) => ({ ...r, vector: vectors[j] }));
1824
+ writeBatch(enriched);
1825
+ chunksEmbedded += batch.length;
1311
1826
  }
1312
- const clusters = Array.from(groups.values());
1313
- return { clusters, noise: [] };
1827
+ return {
1828
+ chunksEmbedded,
1829
+ chunksSkipped,
1830
+ durationMs: Date.now() - start
1831
+ };
1314
1832
  }
1315
- var CCT_PATTERNS_PATH = ".claude/lessons/cct-patterns.jsonl";
1316
- var CctPatternSchema = z.object({
1317
- id: z.string().regex(/^CCT-[a-f0-9]{8}$/),
1318
- name: z.string().min(1),
1319
- description: z.string().min(1),
1320
- frequency: z.number().int().positive(),
1321
- testable: z.boolean(),
1322
- testApproach: z.string().optional(),
1323
- sourceIds: z.array(z.string()).min(1),
1324
- created: z.string()
1325
- // ISO8601
1833
+ var BATCH_SIZE;
1834
+ var init_embed_chunks = __esm({
1835
+ "src/memory/knowledge/embed-chunks.ts"() {
1836
+ init_nomic();
1837
+ init_connection2();
1838
+ BATCH_SIZE = 16;
1839
+ }
1326
1840
  });
1327
- function generateCctId(input) {
1328
- const hash = createHash("sha256").update(input).digest("hex");
1329
- return `CCT-${hash.slice(0, 8)}`;
1330
- }
1331
1841
 
1332
- // src/compound/io.ts
1333
- async function readCctPatterns(repoRoot) {
1334
- const filePath = join(repoRoot, CCT_PATTERNS_PATH);
1335
- let content;
1842
+ // src/memory/knowledge/indexing.ts
1843
+ var indexing_exports = {};
1844
+ __export(indexing_exports, {
1845
+ indexDocs: () => indexDocs
1846
+ });
1847
+ function fileHash(content) {
1848
+ return createHash("sha256").update(content).digest("hex");
1849
+ }
1850
+ function fileHashKey(relativePath) {
1851
+ return "file_hash:" + relativePath;
1852
+ }
1853
+ function getStoredFileHash(repoRoot, relativePath) {
1854
+ const db = openKnowledgeDb(repoRoot);
1855
+ const row = db.prepare("SELECT value FROM metadata WHERE key = ?").get(fileHashKey(relativePath));
1856
+ return row?.value ?? null;
1857
+ }
1858
+ function setFileHash(repoRoot, relativePath, hash) {
1859
+ const db = openKnowledgeDb(repoRoot);
1860
+ db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run(fileHashKey(relativePath), hash);
1861
+ }
1862
+ function removeFileHash(repoRoot, relativePath) {
1863
+ const db = openKnowledgeDb(repoRoot);
1864
+ db.prepare("DELETE FROM metadata WHERE key = ?").run(fileHashKey(relativePath));
1865
+ }
1866
+ async function walkSupportedFiles(baseDir, repoRoot) {
1867
+ const results = [];
1868
+ let entries;
1336
1869
  try {
1337
- content = await readFile(filePath, "utf-8");
1338
- } catch (err) {
1339
- if (err.code === "ENOENT") {
1340
- return [];
1341
- }
1342
- throw err;
1870
+ entries = await readdir(baseDir, { recursive: true, withFileTypes: true });
1871
+ } catch {
1872
+ return results;
1343
1873
  }
1344
- const patterns = [];
1345
- const lines = content.split("\n");
1346
- for (const line of lines) {
1347
- const trimmed = line.trim();
1348
- if (!trimmed) continue;
1349
- const parsed = JSON.parse(trimmed);
1350
- const result = CctPatternSchema.safeParse(parsed);
1351
- if (result.success) {
1352
- patterns.push(result.data);
1353
- }
1874
+ for (const entry of entries) {
1875
+ if (!entry.isFile()) continue;
1876
+ const ext = extname(entry.name).toLowerCase();
1877
+ if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
1878
+ const fullPath = join(entry.parentPath ?? entry.path, entry.name);
1879
+ const relPath = relative(repoRoot, fullPath);
1880
+ results.push(relPath);
1354
1881
  }
1355
- return patterns;
1882
+ return results;
1356
1883
  }
1357
- async function writeCctPatterns(repoRoot, patterns) {
1358
- const filePath = join(repoRoot, CCT_PATTERNS_PATH);
1359
- await mkdir(dirname(filePath), { recursive: true });
1360
- const lines = patterns.map((p) => JSON.stringify(p) + "\n").join("");
1361
- await appendFile(filePath, lines, "utf-8");
1884
+ async function tryEmbedChunks(repoRoot) {
1885
+ const { isModelUsable: isModelUsable2 } = await Promise.resolve().then(() => (init_model(), model_exports));
1886
+ const usability = await isModelUsable2();
1887
+ if (!usability.usable) {
1888
+ throw new Error(`Embedding failed: ${usability.reason}. ${usability.action}`);
1889
+ }
1890
+ const { embedChunks: embedChunks2 } = await Promise.resolve().then(() => (init_embed_chunks(), embed_chunks_exports));
1891
+ const embedResult = await embedChunks2(repoRoot);
1892
+ return embedResult.chunksEmbedded;
1362
1893
  }
1363
-
1364
- // src/compound/synthesis.ts
1365
- function synthesizePattern(cluster, clusterId) {
1366
- const id = generateCctId(clusterId);
1367
- const frequency = cluster.length;
1368
- const sourceIds = cluster.map((item) => item.id);
1369
- const tagCounts = /* @__PURE__ */ new Map();
1370
- for (const item of cluster) {
1371
- for (const tag of item.tags) {
1372
- tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
1894
+ async function indexDocs(repoRoot, options = {}) {
1895
+ const start = Date.now();
1896
+ const docsDir = options.docsDir ?? "docs";
1897
+ const force = options.force ?? false;
1898
+ const stats = {
1899
+ filesIndexed: 0,
1900
+ filesSkipped: 0,
1901
+ filesErrored: 0,
1902
+ chunksCreated: 0,
1903
+ chunksDeleted: 0,
1904
+ chunksEmbedded: 0,
1905
+ durationMs: 0
1906
+ };
1907
+ const docsPath = join(repoRoot, docsDir);
1908
+ const filePaths = await walkSupportedFiles(docsPath, repoRoot);
1909
+ for (const relPath of filePaths) {
1910
+ const fullPath = join(repoRoot, relPath);
1911
+ let content;
1912
+ try {
1913
+ content = await readFile(fullPath, "utf-8");
1914
+ } catch {
1915
+ stats.filesErrored++;
1916
+ continue;
1373
1917
  }
1918
+ const hash = fileHash(content);
1919
+ const storedHash = getStoredFileHash(repoRoot, relPath);
1920
+ if (!force && storedHash === hash) {
1921
+ stats.filesSkipped++;
1922
+ continue;
1923
+ }
1924
+ const chunks = chunkFile(relPath, content);
1925
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1926
+ const knowledgeChunks = chunks.map((chunk) => ({
1927
+ id: chunk.id,
1928
+ filePath: chunk.filePath,
1929
+ startLine: chunk.startLine,
1930
+ endLine: chunk.endLine,
1931
+ contentHash: chunk.contentHash,
1932
+ text: chunk.text,
1933
+ updatedAt: now
1934
+ }));
1935
+ const db = openKnowledgeDb(repoRoot);
1936
+ db.transaction(() => {
1937
+ deleteChunksByFilePath(repoRoot, [relPath]);
1938
+ if (knowledgeChunks.length > 0) {
1939
+ upsertChunks(repoRoot, knowledgeChunks);
1940
+ }
1941
+ setFileHash(repoRoot, relPath, hash);
1942
+ })();
1943
+ stats.filesIndexed++;
1944
+ stats.chunksCreated += knowledgeChunks.length;
1374
1945
  }
1375
- const sortedTags = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).map(([tag]) => tag);
1376
- const name = sortedTags.length > 0 ? sortedTags.slice(0, 3).join(", ") : cluster[0].insight.slice(0, 50);
1377
- const description = cluster.map((item) => item.insight).join("; ");
1378
- const hasHighSeverity = cluster.some(
1379
- (item) => "severity" in item && item.severity === "high"
1380
- );
1381
- const hasEvidence = cluster.some(
1382
- (item) => "evidence" in item && item.evidence
1383
- );
1384
- const testable = hasHighSeverity || hasEvidence;
1385
- const testApproach = testable ? `Verify pattern: ${name}. Check ${frequency} related lesson(s).` : void 0;
1386
- return {
1387
- id,
1388
- name,
1389
- description,
1390
- frequency,
1391
- testable,
1392
- ...testApproach !== void 0 && { testApproach },
1393
- sourceIds,
1394
- created: (/* @__PURE__ */ new Date()).toISOString()
1395
- };
1396
- }
1397
-
1398
- // src/memory/search/vector.ts
1399
- var cctEmbeddingCache = /* @__PURE__ */ new Map();
1400
- function cosineSimilarity(a, b) {
1401
- if (a.length !== b.length) {
1402
- throw new Error("Vectors must have same length");
1946
+ const indexedPaths = getIndexedFilePaths(repoRoot);
1947
+ const currentPathSet = new Set(filePaths);
1948
+ const stalePaths = indexedPaths.filter((p) => !currentPathSet.has(p));
1949
+ if (stalePaths.length > 0) {
1950
+ for (const path of stalePaths) {
1951
+ stats.chunksDeleted += getChunkCountByFilePath(repoRoot, path);
1952
+ }
1953
+ deleteChunksByFilePath(repoRoot, stalePaths);
1954
+ for (const path of stalePaths) {
1955
+ removeFileHash(repoRoot, path);
1956
+ }
1403
1957
  }
1404
- let dotProduct = 0;
1405
- let normA = 0;
1406
- let normB = 0;
1407
- for (let i = 0; i < a.length; i++) {
1408
- dotProduct += a[i] * b[i];
1409
- normA += a[i] * a[i];
1410
- normB += b[i] * b[i];
1958
+ setLastIndexTime(repoRoot, (/* @__PURE__ */ new Date()).toISOString());
1959
+ if (options.embed) {
1960
+ stats.chunksEmbedded = await tryEmbedChunks(repoRoot);
1411
1961
  }
1412
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
1413
- if (magnitude === 0) return 0;
1414
- return dotProduct / magnitude;
1962
+ stats.durationMs = Date.now() - start;
1963
+ return stats;
1415
1964
  }
1416
- var DEFAULT_LIMIT = 10;
1417
- function cctToMemoryItem(pattern) {
1418
- return {
1419
- id: pattern.id,
1420
- type: "lesson",
1421
- trigger: pattern.name,
1422
- insight: pattern.description,
1423
- tags: [],
1424
- source: "manual",
1425
- context: { tool: "compound", intent: "synthesis" },
1426
- created: pattern.created,
1427
- confirmed: true,
1428
- supersedes: [],
1429
- related: pattern.sourceIds
1430
- };
1965
+ var init_indexing = __esm({
1966
+ "src/memory/knowledge/indexing.ts"() {
1967
+ init_connection2();
1968
+ init_sync2();
1969
+ init_chunking();
1970
+ init_types3();
1971
+ }
1972
+ });
1973
+ function lockPath(repoRoot) {
1974
+ return join(repoRoot, ".claude", ".cache", "embed.lock");
1431
1975
  }
1432
- async function searchVector(repoRoot, query, options) {
1433
- const limit = options?.limit ?? DEFAULT_LIMIT;
1434
- const { items } = await readMemoryItems(repoRoot);
1435
- let cctPatterns = [];
1976
+ function lockDir(repoRoot) {
1977
+ return join(repoRoot, ".claude", ".cache");
1978
+ }
1979
+ function isProcessAlive(pid) {
1436
1980
  try {
1437
- cctPatterns = await readCctPatterns(repoRoot);
1981
+ process.kill(pid, 0);
1982
+ return true;
1438
1983
  } catch {
1984
+ return false;
1439
1985
  }
1440
- if (items.length === 0 && cctPatterns.length === 0) return [];
1441
- const queryVector = await embedText(query);
1442
- const scored = [];
1443
- for (const item of items) {
1444
- if (item.invalidatedAt) continue;
1986
+ }
1987
+ function readLock(filePath) {
1988
+ try {
1989
+ const raw = readFileSync(filePath, "utf-8");
1990
+ const parsed = JSON.parse(raw);
1991
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.pid === "number" && typeof parsed.startedAt === "string") {
1992
+ return parsed;
1993
+ }
1994
+ return null;
1995
+ } catch {
1996
+ return null;
1997
+ }
1998
+ }
1999
+ function acquireEmbedLock(repoRoot) {
2000
+ const dir = lockDir(repoRoot);
2001
+ const file = lockPath(repoRoot);
2002
+ const content = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
2003
+ mkdirSync(dir, { recursive: true });
2004
+ try {
2005
+ writeFileSync(file, JSON.stringify(content), { flag: "wx" });
2006
+ return { acquired: true, release: () => releaseLock(file) };
2007
+ } catch (err) {
2008
+ if (err.code !== "EEXIST") throw err;
2009
+ const existing = readLock(file);
2010
+ if (existing && isProcessAlive(existing.pid)) {
2011
+ return { acquired: false, holder: existing.pid };
2012
+ }
1445
2013
  try {
1446
- const itemText = `${item.trigger} ${item.insight}`;
1447
- const hash = contentHash(item.trigger, item.insight);
1448
- let itemVector = getCachedEmbedding(repoRoot, item.id, hash);
1449
- if (!itemVector) {
1450
- itemVector = await embedText(itemText);
1451
- setCachedEmbedding(repoRoot, item.id, itemVector, hash);
1452
- }
1453
- const score = cosineSimilarity(queryVector, itemVector);
1454
- scored.push({ lesson: item, score });
2014
+ unlinkSync(file);
1455
2015
  } catch {
1456
- continue;
1457
2016
  }
1458
- }
1459
- for (const pattern of cctPatterns) {
1460
2017
  try {
1461
- const text = `${pattern.name} ${pattern.description}`;
1462
- const hash = contentHash(pattern.name, pattern.description);
1463
- const cacheKey = `${pattern.id}:${hash}`;
1464
- let vec = cctEmbeddingCache.get(cacheKey);
1465
- if (!vec) {
1466
- vec = await embedText(text);
1467
- cctEmbeddingCache.set(cacheKey, vec);
1468
- }
1469
- const score = cosineSimilarity(queryVector, vec);
1470
- scored.push({ lesson: cctToMemoryItem(pattern), score });
2018
+ writeFileSync(file, JSON.stringify(content), { flag: "wx" });
2019
+ return { acquired: true, release: () => releaseLock(file) };
1471
2020
  } catch {
1472
- continue;
2021
+ const winner = readLock(file);
2022
+ return { acquired: false, holder: winner?.pid ?? -1 };
1473
2023
  }
1474
2024
  }
1475
- scored.sort((a, b) => b.score - a.score);
1476
- return scored.slice(0, limit);
1477
2025
  }
1478
-
1479
- // src/memory/search/ranking.ts
1480
- var RECENCY_THRESHOLD_DAYS = 30;
1481
- var HIGH_SEVERITY_BOOST = 1.5;
1482
- var MEDIUM_SEVERITY_BOOST = 1;
1483
- var LOW_SEVERITY_BOOST = 0.8;
1484
- var RECENCY_BOOST = 1.2;
1485
- var CONFIRMATION_BOOST = 1.3;
1486
- var MAX_COMBINED_BOOST = 1.8;
1487
- function severityBoost(item) {
1488
- switch (item.severity) {
1489
- case "high":
1490
- return HIGH_SEVERITY_BOOST;
1491
- case "medium":
1492
- return MEDIUM_SEVERITY_BOOST;
1493
- case "low":
1494
- return LOW_SEVERITY_BOOST;
1495
- default:
1496
- return MEDIUM_SEVERITY_BOOST;
2026
+ function isEmbedLocked(repoRoot) {
2027
+ const file = lockPath(repoRoot);
2028
+ if (!existsSync(file)) return false;
2029
+ const content = readLock(file);
2030
+ if (!content) return false;
2031
+ return isProcessAlive(content.pid);
2032
+ }
2033
+ function releaseLock(file) {
2034
+ try {
2035
+ unlinkSync(file);
2036
+ } catch {
1497
2037
  }
1498
2038
  }
1499
- function recencyBoost(item) {
1500
- const ageDays = getLessonAgeDays(item);
1501
- return ageDays <= RECENCY_THRESHOLD_DAYS ? RECENCY_BOOST : 1;
2039
+ var init_embed_lock = __esm({
2040
+ "src/memory/knowledge/embed-lock.ts"() {
2041
+ }
2042
+ });
2043
+ function statusPath(repoRoot) {
2044
+ return join(repoRoot, STATUS_FILE);
1502
2045
  }
1503
- function confirmationBoost(item) {
1504
- return item.confirmed ? CONFIRMATION_BOOST : 1;
2046
+ function writeEmbedStatus(repoRoot, status) {
2047
+ const filePath = statusPath(repoRoot);
2048
+ mkdirSync(dirname(filePath), { recursive: true });
2049
+ writeFileSync(filePath, JSON.stringify(status, null, 2), "utf-8");
1505
2050
  }
1506
- function calculateScore(item, vectorSimilarity) {
1507
- const boost = Math.min(
1508
- severityBoost(item) * recencyBoost(item) * confirmationBoost(item),
1509
- MAX_COMBINED_BOOST
1510
- );
1511
- return vectorSimilarity * boost;
2051
+ function readEmbedStatus(repoRoot) {
2052
+ try {
2053
+ const raw = readFileSync(statusPath(repoRoot), "utf-8");
2054
+ return JSON.parse(raw);
2055
+ } catch {
2056
+ return null;
2057
+ }
1512
2058
  }
1513
- function rankLessons(lessons) {
1514
- return lessons.map((scored) => ({
1515
- ...scored,
1516
- finalScore: calculateScore(scored.lesson, scored.score)
1517
- })).sort((a, b) => (b.finalScore ?? 0) - (a.finalScore ?? 0));
2059
+ var STATUS_FILE;
2060
+ var init_embed_status = __esm({
2061
+ "src/memory/knowledge/embed-status.ts"() {
2062
+ STATUS_FILE = ".claude/.cache/embed-status.json";
2063
+ }
2064
+ });
2065
+ function resolveCliInvocation() {
2066
+ let dir = dirname(fileURLToPath(import.meta.url));
2067
+ for (let i = 0; i < 10; i++) {
2068
+ const candidate = join(dir, "dist", "cli.js");
2069
+ if (existsSync(candidate)) {
2070
+ return { command: process.execPath, args: [candidate] };
2071
+ }
2072
+ const parent = dirname(dir);
2073
+ if (parent === dir) break;
2074
+ dir = parent;
2075
+ }
2076
+ return { command: "npx", args: ["ca"] };
1518
2077
  }
2078
+ function spawnBackgroundEmbed(repoRoot) {
2079
+ if (isEmbedLocked(repoRoot)) {
2080
+ return { spawned: false, reason: "Embedding already in progress" };
2081
+ }
2082
+ if (!isModelAvailable()) {
2083
+ return { spawned: false, reason: "Model not available" };
2084
+ }
2085
+ if (getUnembeddedChunkCount(repoRoot) === 0) {
2086
+ return { spawned: false, reason: "All chunks already embedded" };
2087
+ }
2088
+ const cli = resolveCliInvocation();
2089
+ const child = spawn(cli.command, [...cli.args, "embed-worker", repoRoot], {
2090
+ detached: true,
2091
+ stdio: "ignore"
2092
+ });
2093
+ child.unref();
2094
+ return { spawned: true, pid: child.pid };
2095
+ }
2096
+ async function runBackgroundEmbed(repoRoot) {
2097
+ const lock = acquireEmbedLock(repoRoot);
2098
+ if (!lock.acquired) return;
2099
+ const { openKnowledgeDb: openKnowledgeDb2 } = await Promise.resolve().then(() => (init_sqlite_knowledge(), sqlite_knowledge_exports));
2100
+ openKnowledgeDb2(repoRoot);
2101
+ const start = Date.now();
2102
+ writeEmbedStatus(repoRoot, { state: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString() });
2103
+ try {
2104
+ const result = await embedChunks(repoRoot, { onlyMissing: true });
2105
+ writeEmbedStatus(repoRoot, {
2106
+ state: "completed",
2107
+ chunksEmbedded: result.chunksEmbedded,
2108
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2109
+ durationMs: result.durationMs
2110
+ });
2111
+ } catch (err) {
2112
+ const msg = err instanceof Error ? err.message : "Unknown error";
2113
+ writeEmbedStatus(repoRoot, {
2114
+ state: "failed",
2115
+ error: msg,
2116
+ durationMs: Date.now() - start
2117
+ });
2118
+ } finally {
2119
+ unloadEmbedding();
2120
+ closeKnowledgeDb();
2121
+ lock.release();
2122
+ }
2123
+ }
2124
+ async function indexAndSpawnEmbed(repoRoot) {
2125
+ const docsPath = join(repoRoot, "docs");
2126
+ if (!existsSync(docsPath)) return null;
2127
+ const { indexDocs: indexDocs2 } = await Promise.resolve().then(() => (init_indexing(), indexing_exports));
2128
+ await indexDocs2(repoRoot);
2129
+ return spawnBackgroundEmbed(repoRoot);
2130
+ }
2131
+ var init_embed_background = __esm({
2132
+ "src/memory/knowledge/embed-background.ts"() {
2133
+ init_embeddings();
2134
+ init_sqlite_knowledge();
2135
+ init_embed_lock();
2136
+ init_embed_status();
2137
+ init_embed_chunks();
2138
+ }
2139
+ });
2140
+ var _require = createRequire(import.meta.url);
2141
+ var _pkg = _require("../package.json");
2142
+ var VERSION = _pkg.version;
2143
+
2144
+ // src/index.ts
2145
+ init_storage();
2146
+ init_embeddings();
2147
+ init_search2();
1519
2148
 
1520
2149
  // src/memory/capture/quality.ts
1521
- var DEFAULT_SIMILARITY_THRESHOLD = 0.8;
2150
+ init_model();
2151
+ init_search2();
2152
+ init_storage();
2153
+ var DUPLICATE_THRESHOLD = 0.98;
1522
2154
  async function isNovel(repoRoot, insight, options = {}) {
1523
- const threshold = options.threshold ?? DEFAULT_SIMILARITY_THRESHOLD;
1524
- await syncIfNeeded(repoRoot);
1525
- const words = insight.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3).slice(0, 3);
1526
- if (words.length === 0) {
1527
- return { novel: true };
1528
- }
1529
- const searchQuery = words.join(" OR ");
1530
- const results = await searchKeyword(repoRoot, searchQuery, 10);
1531
- if (results.length === 0) {
2155
+ const threshold = options.threshold ?? DUPLICATE_THRESHOLD;
2156
+ if (!isModelAvailable()) {
1532
2157
  return { novel: true };
1533
2158
  }
1534
- return checkSimilarity(insight, results, threshold);
1535
- }
1536
- function checkSimilarity(insight, lessons, threshold) {
1537
- const insightWords = new Set(insight.toLowerCase().split(/\s+/));
1538
- for (const lesson of lessons) {
1539
- const lessonWords = new Set(lesson.insight.toLowerCase().split(/\s+/));
1540
- const intersection = [...insightWords].filter((w) => lessonWords.has(w)).length;
1541
- const union = (/* @__PURE__ */ new Set([...insightWords, ...lessonWords])).size;
1542
- const similarity = union > 0 ? intersection / union : 0;
1543
- if (similarity >= threshold) {
2159
+ try {
2160
+ await syncIfNeeded(repoRoot);
2161
+ const similar = await findSimilarLessons(repoRoot, insight, { threshold });
2162
+ const top = similar[0];
2163
+ if (top) {
1544
2164
  return {
1545
2165
  novel: false,
1546
- reason: `Found similar existing lesson: "${lesson.insight.slice(0, 50)}..."`,
1547
- existingId: lesson.id
2166
+ reason: `Near-duplicate of existing lesson: "${top.item.insight.slice(0, 50)}..."`,
2167
+ existingId: top.item.id
1548
2168
  };
1549
2169
  }
2170
+ return { novel: true };
2171
+ } catch (err) {
2172
+ if (process.env["CA_DEBUG"]) {
2173
+ process.stderr.write(`[CA_DEBUG] isNovel catch: ${err instanceof Error ? err.message : String(err)}
2174
+ `);
2175
+ }
2176
+ return { novel: true };
1550
2177
  }
1551
- return { novel: true };
1552
2178
  }
1553
2179
  var MIN_WORD_COUNT = 4;
1554
2180
  var VAGUE_PATTERNS = [
@@ -1675,6 +2301,9 @@ function detectTestFailure(testResult) {
1675
2301
  trigger: `Test failure in ${testResult.testFile}: ${errorLine.slice(0, 100)}`
1676
2302
  };
1677
2303
  }
2304
+
2305
+ // src/memory/capture/integration.ts
2306
+ init_types();
1678
2307
  var CorrectionSignalSchema = z.object({
1679
2308
  messages: z.array(z.string()),
1680
2309
  context: ContextSchema
@@ -1699,6 +2328,7 @@ z.discriminatedUnion("type", [
1699
2328
  ]);
1700
2329
 
1701
2330
  // src/memory/retrieval/session.ts
2331
+ init_storage();
1702
2332
  var DEFAULT_LIMIT2 = 5;
1703
2333
  function hasSeverity(item) {
1704
2334
  return item.severity !== void 0;
@@ -1721,6 +2351,8 @@ async function loadSessionLessons(repoRoot, limit = DEFAULT_LIMIT2) {
1721
2351
  }
1722
2352
 
1723
2353
  // src/memory/retrieval/plan.ts
2354
+ init_search2();
2355
+ init_storage();
1724
2356
  var DEFAULT_LIMIT3 = 5;
1725
2357
  async function retrieveForPlan(repoRoot, planText, limit = DEFAULT_LIMIT3) {
1726
2358
  const candidateLimit = limit * CANDIDATE_MULTIPLIER;
@@ -1752,95 +2384,21 @@ No relevant lessons found for this plan.`;
1752
2384
  ${lessonLines.join("\n")}`;
1753
2385
  }
1754
2386
 
1755
- // src/memory/storage/sqlite-knowledge/index.ts
1756
- init_connection();
1757
- init_schema();
1758
-
1759
- // src/memory/storage/sqlite-knowledge/cache.ts
1760
- init_connection();
1761
- init_types();
1762
- function getCachedChunkEmbedding(repoRoot, chunkId, expectedHash) {
1763
- const database = openKnowledgeDb(repoRoot);
1764
- const row = database.prepare("SELECT embedding, content_hash FROM chunks WHERE id = ?").get(chunkId);
1765
- if (!row || !row.embedding || !row.content_hash) {
1766
- return null;
1767
- }
1768
- if (expectedHash && row.content_hash !== expectedHash) {
1769
- return null;
1770
- }
1771
- return new Float32Array(
1772
- row.embedding.buffer,
1773
- row.embedding.byteOffset,
1774
- row.embedding.byteLength / 4
1775
- );
1776
- }
1777
- function setCachedChunkEmbedding(repoRoot, chunkId, embedding, hash) {
1778
- const database = openKnowledgeDb(repoRoot);
1779
- const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
1780
- const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
1781
- database.prepare("UPDATE chunks SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, chunkId);
1782
- }
1783
- function collectCachedChunkEmbeddings(database) {
1784
- const cache = /* @__PURE__ */ new Map();
1785
- const rows = database.prepare("SELECT id, embedding, content_hash FROM chunks WHERE embedding IS NOT NULL").all();
1786
- for (const row of rows) {
1787
- if (row.embedding && row.content_hash) {
1788
- cache.set(row.id, { embedding: row.embedding, contentHash: row.content_hash });
1789
- }
1790
- }
1791
- return cache;
1792
- }
1793
-
1794
- // src/memory/storage/sqlite-knowledge/search.ts
1795
- init_connection();
1796
- function rowToChunk(row) {
1797
- const chunk = {
1798
- id: row.id,
1799
- filePath: row.file_path,
1800
- startLine: row.start_line,
1801
- endLine: row.end_line,
1802
- contentHash: row.content_hash,
1803
- text: row.text,
1804
- updatedAt: row.updated_at
1805
- };
1806
- if (row.model !== null) {
1807
- chunk.model = row.model;
1808
- }
1809
- return chunk;
1810
- }
1811
- function searchChunksKeywordScored(repoRoot, query, limit) {
1812
- const database = openKnowledgeDb(repoRoot);
1813
- const sanitized = sanitizeFtsQuery(query);
1814
- if (sanitized === "") return [];
1815
- try {
1816
- const rows = database.prepare(
1817
- `SELECT c.*, fts.rank
1818
- FROM chunks c
1819
- JOIN chunks_fts fts ON c.rowid = fts.rowid
1820
- WHERE chunks_fts MATCH ?
1821
- ORDER BY fts.rank
1822
- LIMIT ?`
1823
- ).all(sanitized, limit);
1824
- return rows.map((row) => ({
1825
- chunk: rowToChunk(row),
1826
- score: normalizeBm25Rank(row.rank)
1827
- }));
1828
- } catch (err) {
1829
- const message = err instanceof Error ? err.message : "Unknown FTS5 error";
1830
- console.error(`[compound-agent] knowledge scored search error: ${message}`);
1831
- return [];
1832
- }
1833
- }
1834
-
1835
- // src/memory/storage/sqlite-knowledge/index.ts
1836
- init_sync();
1837
-
1838
2387
  // src/index.ts
2388
+ init_sqlite_knowledge();
2389
+
2390
+ // src/memory/knowledge/index.ts
1839
2391
  init_chunking();
2392
+ init_types3();
1840
2393
  init_indexing();
1841
2394
 
1842
2395
  // src/memory/knowledge/search.ts
1843
- init_connection();
2396
+ init_connection2();
2397
+ init_search3();
2398
+ init_nomic();
2399
+ init_vector();
2400
+ init_hybrid();
2401
+ init_model();
1844
2402
  var DEFAULT_KNOWLEDGE_LIMIT = 6;
1845
2403
  async function searchKnowledgeVector(repoRoot, query, options) {
1846
2404
  const limit = options?.limit ?? DEFAULT_KNOWLEDGE_LIMIT;
@@ -1912,10 +2470,23 @@ async function searchKnowledge(repoRoot, query, options) {
1912
2470
  return keywordResults.map((k) => ({ item: k.chunk, score: k.score }));
1913
2471
  }
1914
2472
 
2473
+ // src/memory/knowledge/index.ts
2474
+ init_embed_chunks();
2475
+ init_embed_lock();
2476
+ init_embed_status();
2477
+ init_embed_background();
2478
+
1915
2479
  // src/cli-utils.ts
1916
2480
  function getRepoRoot() {
1917
2481
  return process.env["COMPOUND_AGENT_ROOT"] || process.cwd();
1918
2482
  }
2483
+
2484
+ // src/setup/all.ts
2485
+ init_embeddings();
2486
+ init_storage();
2487
+
2488
+ // src/setup/display-utils.ts
2489
+ init_storage();
1919
2490
  var STATE_DIR = ".claude";
1920
2491
  var STATE_FILE = ".ca-phase-state.json";
1921
2492
  var PHASE_STATE_MAX_AGE_MS = 72 * 60 * 60 * 1e3;
@@ -1944,9 +2515,9 @@ function validatePhaseState(raw) {
1944
2515
  }
1945
2516
  function getPhaseState(repoRoot) {
1946
2517
  try {
1947
- const path2 = getStatePath(repoRoot);
1948
- if (!existsSync(path2)) return null;
1949
- const raw = readFileSync(path2, "utf-8");
2518
+ const path = getStatePath(repoRoot);
2519
+ if (!existsSync(path)) return null;
2520
+ const raw = readFileSync(path, "utf-8");
1950
2521
  const parsed = JSON.parse(raw);
1951
2522
  if (!validatePhaseState(parsed)) return null;
1952
2523
  const age = Date.now() - new Date(parsed.started_at).getTime();
@@ -1961,13 +2532,41 @@ function getPhaseState(repoRoot) {
1961
2532
  }
1962
2533
  function cleanPhaseState(repoRoot) {
1963
2534
  try {
1964
- const path2 = getStatePath(repoRoot);
1965
- if (existsSync(path2)) unlinkSync(path2);
2535
+ const path = getStatePath(repoRoot);
2536
+ if (existsSync(path)) unlinkSync(path);
1966
2537
  } catch {
1967
2538
  }
1968
2539
  }
2540
+ init_storage();
2541
+ init_embeddings();
2542
+ init_storage();
2543
+
2544
+ // src/commands/management-crud.ts
2545
+ init_storage();
2546
+
2547
+ // src/memory/index.ts
2548
+ init_types();
2549
+ init_storage();
2550
+ init_embeddings();
2551
+ init_search2();
2552
+ init_storage();
2553
+
2554
+ // src/commands/shared.ts
2555
+ init_utils();
2556
+
2557
+ // src/commands/management-helpers.ts
2558
+ init_storage();
2559
+ init_embeddings();
2560
+ init_storage();
2561
+ init_storage();
2562
+
2563
+ // src/commands/management-invalidation.ts
2564
+ init_storage();
2565
+ init_storage();
2566
+ init_storage();
1969
2567
 
1970
2568
  // src/commands/management-prime.ts
2569
+ init_storage();
1971
2570
  var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
1972
2571
 
1973
2572
  > **Context Recovery**: Run \`npx ca prime\` after compaction, clear, or new session
@@ -2083,6 +2682,7 @@ ${formattedLessons}
2083
2682
  }
2084
2683
 
2085
2684
  // src/audit/checks/lessons.ts
2685
+ init_storage();
2086
2686
  async function checkLessons(repoRoot) {
2087
2687
  const { items } = await readMemoryItems(repoRoot);
2088
2688
  const findings = [];
@@ -2100,6 +2700,9 @@ async function checkLessons(repoRoot) {
2100
2700
  const filesChecked = items.length > 0 ? [LESSONS_PATH] : [];
2101
2701
  return { findings, filesChecked };
2102
2702
  }
2703
+
2704
+ // src/audit/checks/patterns.ts
2705
+ init_storage();
2103
2706
  var SeveritySchema2 = z.enum(["error", "warning", "info"]);
2104
2707
  var FilePatternCheckSchema = z.object({
2105
2708
  type: z.literal("file-pattern"),
@@ -2373,11 +2976,24 @@ var AuditReportSchema = z.object({
2373
2976
  timestamp: z.string()
2374
2977
  });
2375
2978
 
2376
- // src/memory/knowledge/index.ts
2377
- init_chunking();
2979
+ // src/commands/knowledge.ts
2980
+ init_sqlite_knowledge();
2981
+
2982
+ // src/commands/knowledge-index.ts
2983
+ init_embeddings();
2984
+ init_sqlite_knowledge();
2985
+
2986
+ // src/commands/clean-lessons.ts
2987
+ init_embeddings();
2988
+ init_search2();
2989
+ init_storage();
2990
+ init_storage();
2991
+ init_search2();
2992
+
2993
+ // src/index.ts
2994
+ init_compound();
2378
2995
  init_types();
2379
- init_indexing();
2380
2996
 
2381
- export { AuditFindingSchema, AuditReportSchema, CANDIDATE_MULTIPLIER, CCT_PATTERNS_PATH, CctPatternSchema, DB_PATH, DEFAULT_TEXT_WEIGHT, DEFAULT_VECTOR_WEIGHT, KNOWLEDGE_DB_PATH, KNOWLEDGE_SCHEMA_VERSION, LESSONS_PATH, LessonItemSchema, LessonSchema, MODEL_FILENAME, MODEL_URI, MemoryItemRecordSchema, MemoryItemSchema, MemoryItemTypeSchema, PatternItemSchema, PreferenceItemSchema, SolutionItemSchema, VERSION, appendLesson, appendMemoryItem, buildSimilarityMatrix, calculateScore, chunkFile, closeDb, closeKnowledgeDb, clusterBySimilarity, collectCachedChunkEmbeddings, confirmationBoost, cosineSimilarity, detectSelfCorrection, detectTestFailure, detectUserCorrection, embedText, embedTexts, formatLessonsCheck, generateId, getCachedChunkEmbedding, getEmbedding, getPrimeContext, indexDocs, isActionable, isModelAvailable, isModelUsable, isNovel, isSpecific, loadSessionLessons, mergeHybridResults, normalizeBm25Rank, openKnowledgeDb, rankLessons, readCctPatterns, readLessons, readMemoryItems, rebuildIndex, recencyBoost, resolveModel, retrieveForPlan, runAudit, searchChunksKeywordScored, searchKeyword, searchKnowledge, searchKnowledgeVector, searchVector, setCachedChunkEmbedding, severityBoost, shouldPropose, synthesizePattern, unloadEmbedding, writeCctPatterns };
2997
+ export { AuditFindingSchema, AuditReportSchema, CANDIDATE_MULTIPLIER, CCT_PATTERNS_PATH, CctPatternSchema, DB_PATH, DEFAULT_TEXT_WEIGHT, DEFAULT_VECTOR_WEIGHT, KNOWLEDGE_DB_PATH, KNOWLEDGE_SCHEMA_VERSION, LESSONS_PATH, LessonItemSchema, LessonSchema, MODEL_FILENAME, MODEL_URI, MemoryItemRecordSchema, MemoryItemSchema, MemoryItemTypeSchema, PatternItemSchema, PreferenceItemSchema, SolutionItemSchema, VERSION, acquireEmbedLock, appendLesson, appendMemoryItem, buildSimilarityMatrix, calculateScore, chunkFile, closeDb, closeKnowledgeDb, clusterBySimilarity, collectCachedChunkEmbeddings, confirmationBoost, cosineSimilarity, detectSelfCorrection, detectTestFailure, detectUserCorrection, embedChunks, embedText, embedTexts, formatLessonsCheck, generateId, getCachedChunkEmbedding, getEmbedding, getPrimeContext, getUnembeddedChunkCount, indexAndSpawnEmbed, indexDocs, isActionable, isEmbedLocked, isModelAvailable, isModelUsable, isNovel, isSpecific, loadSessionLessons, mergeHybridResults, normalizeBm25Rank, openKnowledgeDb, rankLessons, readCctPatterns, readEmbedStatus, readLessons, readMemoryItems, rebuildIndex, recencyBoost, resolveModel, retrieveForPlan, runAudit, runBackgroundEmbed, searchChunksKeywordScored, searchKeyword, searchKnowledge, searchKnowledgeVector, searchVector, setCachedChunkEmbedding, severityBoost, shouldPropose, spawnBackgroundEmbed, synthesizePattern, unloadEmbedding, writeCctPatterns, writeEmbedStatus };
2382
2998
  //# sourceMappingURL=index.js.map
2383
2999
  //# sourceMappingURL=index.js.map