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