compound-agent 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -1
- package/dist/cli.js +1373 -364
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +264 -30
- package/dist/index.js +781 -66
- package/dist/index.js.map +1 -1
- package/docs/research/AgenticAiCodebaseGuide.md +1206 -0
- package/docs/research/BuildingACCompilerAnthropic.md +116 -0
- package/docs/research/HarnessEngineeringOpenAi.md +220 -0
- package/docs/research/code-review/systematic-review-methodology.md +409 -0
- package/docs/research/index.md +64 -0
- package/docs/research/learning-systems/knowledge-compounding-for-agents.md +695 -0
- package/docs/research/property-testing/property-based-testing-and-invariants.md +742 -0
- package/docs/research/tdd/test-driven-development-methodology.md +547 -0
- package/docs/research/test-optimization-strategies.md +401 -0
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -1,15 +1,501 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
|
-
import {
|
|
3
|
-
import { join, dirname, relative } from 'path';
|
|
2
|
+
import { mkdirSync, unlinkSync, existsSync, statSync, readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { join, dirname, extname, relative } from 'path';
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
|
+
import { readFile, mkdir, appendFile, readdir } from 'fs/promises';
|
|
5
6
|
import { z } from 'zod';
|
|
6
|
-
import { existsSync, mkdirSync, unlinkSync, statSync, readFileSync, readdirSync } from 'fs';
|
|
7
7
|
import { getLlama, resolveModelFile } from 'node-llama-cpp';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
|
+
import 'url';
|
|
10
11
|
import 'chalk';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
17
|
+
function ensureSqliteAvailable() {
|
|
18
|
+
if (checked) return;
|
|
19
|
+
try {
|
|
20
|
+
const module = require2("better-sqlite3");
|
|
21
|
+
const Constructor = module.default || module;
|
|
22
|
+
const testDb = new Constructor(":memory:");
|
|
23
|
+
testDb.close();
|
|
24
|
+
DatabaseConstructor = Constructor;
|
|
25
|
+
checked = true;
|
|
26
|
+
} catch (cause) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'better-sqlite3 failed to load.\nRun: npx ca setup (auto-configures pnpm native builds)\nOr manually add to your package.json:\n "pnpm": { "onlyBuiltDependencies": ["better-sqlite3", "node-llama-cpp"] }\nThen run: pnpm install && pnpm rebuild better-sqlite3\nFor npm/yarn, run: npm rebuild better-sqlite3',
|
|
29
|
+
{ cause }
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getDatabaseConstructor() {
|
|
34
|
+
ensureSqliteAvailable();
|
|
35
|
+
return DatabaseConstructor;
|
|
36
|
+
}
|
|
37
|
+
var require2, checked, DatabaseConstructor;
|
|
38
|
+
var init_availability = __esm({
|
|
39
|
+
"src/memory/storage/sqlite/availability.ts"() {
|
|
40
|
+
require2 = createRequire(import.meta.url);
|
|
41
|
+
checked = false;
|
|
42
|
+
DatabaseConstructor = null;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// src/memory/storage/sqlite-knowledge/schema.ts
|
|
47
|
+
function createKnowledgeSchema(database) {
|
|
48
|
+
database.exec(SCHEMA_SQL2);
|
|
49
|
+
database.pragma(`user_version = ${KNOWLEDGE_SCHEMA_VERSION}`);
|
|
50
|
+
}
|
|
51
|
+
var KNOWLEDGE_SCHEMA_VERSION, SCHEMA_SQL2;
|
|
52
|
+
var init_schema = __esm({
|
|
53
|
+
"src/memory/storage/sqlite-knowledge/schema.ts"() {
|
|
54
|
+
KNOWLEDGE_SCHEMA_VERSION = 2;
|
|
55
|
+
SCHEMA_SQL2 = `
|
|
56
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
file_path TEXT NOT NULL,
|
|
59
|
+
start_line INTEGER NOT NULL,
|
|
60
|
+
end_line INTEGER NOT NULL,
|
|
61
|
+
content_hash TEXT NOT NULL,
|
|
62
|
+
text TEXT NOT NULL,
|
|
63
|
+
embedding BLOB,
|
|
64
|
+
model TEXT,
|
|
65
|
+
updated_at TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
|
|
69
|
+
text,
|
|
70
|
+
content='chunks', content_rowid='rowid'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
74
|
+
INSERT INTO chunks_fts(rowid, text)
|
|
75
|
+
VALUES (new.rowid, new.text);
|
|
76
|
+
END;
|
|
77
|
+
|
|
78
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
|
|
79
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, text)
|
|
80
|
+
VALUES ('delete', old.rowid, old.text);
|
|
81
|
+
END;
|
|
82
|
+
|
|
83
|
+
CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
|
|
84
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, text)
|
|
85
|
+
VALUES ('delete', old.rowid, old.text);
|
|
86
|
+
INSERT INTO chunks_fts(rowid, text)
|
|
87
|
+
VALUES (new.rowid, new.text);
|
|
88
|
+
END;
|
|
89
|
+
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_file_path ON chunks(file_path);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
|
93
|
+
key TEXT PRIMARY KEY,
|
|
94
|
+
value TEXT NOT NULL
|
|
95
|
+
);
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
function openKnowledgeDb(repoRoot, options = {}) {
|
|
100
|
+
const { inMemory = false } = options;
|
|
101
|
+
const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, KNOWLEDGE_DB_PATH);
|
|
102
|
+
const cached = knowledgeDbMap.get(key);
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
const Database = getDatabaseConstructor();
|
|
107
|
+
let database;
|
|
108
|
+
if (inMemory) {
|
|
109
|
+
database = new Database(":memory:");
|
|
110
|
+
} else {
|
|
111
|
+
const dir = dirname(key);
|
|
112
|
+
mkdirSync(dir, { recursive: true });
|
|
113
|
+
database = new Database(key);
|
|
114
|
+
const version = database.pragma("user_version", { simple: true });
|
|
115
|
+
if (version !== 0 && version !== KNOWLEDGE_SCHEMA_VERSION) {
|
|
116
|
+
database.close();
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(key);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
database = new Database(key);
|
|
122
|
+
}
|
|
123
|
+
database.pragma("journal_mode = WAL");
|
|
124
|
+
}
|
|
125
|
+
createKnowledgeSchema(database);
|
|
126
|
+
knowledgeDbMap.set(key, database);
|
|
127
|
+
return database;
|
|
128
|
+
}
|
|
129
|
+
function closeKnowledgeDb() {
|
|
130
|
+
for (const database of knowledgeDbMap.values()) {
|
|
131
|
+
database.close();
|
|
132
|
+
}
|
|
133
|
+
knowledgeDbMap.clear();
|
|
134
|
+
}
|
|
135
|
+
var KNOWLEDGE_DB_PATH, knowledgeDbMap;
|
|
136
|
+
var init_connection = __esm({
|
|
137
|
+
"src/memory/storage/sqlite-knowledge/connection.ts"() {
|
|
138
|
+
init_availability();
|
|
139
|
+
init_schema();
|
|
140
|
+
KNOWLEDGE_DB_PATH = ".claude/.cache/knowledge.sqlite";
|
|
141
|
+
knowledgeDbMap = /* @__PURE__ */ new Map();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
function generateChunkId(filePath, startLine, endLine) {
|
|
145
|
+
return createHash("sha256").update(`${filePath}:${startLine}:${endLine}`).digest("hex").slice(0, 16);
|
|
146
|
+
}
|
|
147
|
+
function chunkContentHash(text) {
|
|
148
|
+
return createHash("sha256").update(text).digest("hex");
|
|
149
|
+
}
|
|
150
|
+
var SUPPORTED_EXTENSIONS, CODE_EXTENSIONS;
|
|
151
|
+
var init_types = __esm({
|
|
152
|
+
"src/memory/knowledge/types.ts"() {
|
|
153
|
+
SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
154
|
+
".md",
|
|
155
|
+
".txt",
|
|
156
|
+
".rst",
|
|
157
|
+
".ts",
|
|
158
|
+
".py",
|
|
159
|
+
".js",
|
|
160
|
+
".tsx",
|
|
161
|
+
".jsx"
|
|
162
|
+
]);
|
|
163
|
+
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
164
|
+
".ts",
|
|
165
|
+
".tsx",
|
|
166
|
+
".js",
|
|
167
|
+
".jsx",
|
|
168
|
+
".py"
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
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);
|
|
209
|
+
}
|
|
210
|
+
function getIndexedFilePaths(repoRoot) {
|
|
211
|
+
const database = openKnowledgeDb(repoRoot);
|
|
212
|
+
const rows = database.prepare("SELECT DISTINCT file_path FROM chunks").all();
|
|
213
|
+
return rows.map((r) => r.file_path);
|
|
214
|
+
}
|
|
215
|
+
function getChunkCountByFilePath(repoRoot, filePath) {
|
|
216
|
+
const database = openKnowledgeDb(repoRoot);
|
|
217
|
+
const row = database.prepare("SELECT COUNT(*) as cnt FROM chunks WHERE file_path = ?").get(filePath);
|
|
218
|
+
return row.cnt;
|
|
219
|
+
}
|
|
220
|
+
function setLastIndexTime(repoRoot, time) {
|
|
221
|
+
const database = openKnowledgeDb(repoRoot);
|
|
222
|
+
database.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES ('last_index_time', ?)").run(time);
|
|
223
|
+
}
|
|
224
|
+
var init_sync = __esm({
|
|
225
|
+
"src/memory/storage/sqlite-knowledge/sync.ts"() {
|
|
226
|
+
init_connection();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
function isBinary(content) {
|
|
230
|
+
return content.includes("\0");
|
|
231
|
+
}
|
|
232
|
+
function splitIntoSections(fileLines, ext) {
|
|
233
|
+
if (ext === ".md") {
|
|
234
|
+
return splitMarkdown(fileLines);
|
|
235
|
+
}
|
|
236
|
+
if (ext === ".rst") {
|
|
237
|
+
return splitParagraphs(fileLines);
|
|
238
|
+
}
|
|
239
|
+
if (CODE_EXTENSIONS.has(ext)) {
|
|
240
|
+
return splitCode(fileLines);
|
|
241
|
+
}
|
|
242
|
+
return splitParagraphs(fileLines);
|
|
243
|
+
}
|
|
244
|
+
function splitMarkdown(fileLines) {
|
|
245
|
+
const sections = [];
|
|
246
|
+
let current = [];
|
|
247
|
+
let inCodeBlock = false;
|
|
248
|
+
for (let i = 0; i < fileLines.length; i++) {
|
|
249
|
+
const line = fileLines[i];
|
|
250
|
+
const lineObj = { lineNumber: i + 1, text: line };
|
|
251
|
+
if (line.trimStart().startsWith("```")) {
|
|
252
|
+
inCodeBlock = !inCodeBlock;
|
|
253
|
+
current.push(lineObj);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (!inCodeBlock && /^#{2,}\s/.test(line) && current.length > 0) {
|
|
257
|
+
sections.push(current);
|
|
258
|
+
current = [lineObj];
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!inCodeBlock && line.trim() === "" && current.length > 0 && current.some((l) => l.text.trim() !== "")) {
|
|
262
|
+
current.push(lineObj);
|
|
263
|
+
sections.push(current);
|
|
264
|
+
current = [];
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
current.push(lineObj);
|
|
268
|
+
}
|
|
269
|
+
if (current.length > 0) {
|
|
270
|
+
sections.push(current);
|
|
271
|
+
}
|
|
272
|
+
return sections;
|
|
273
|
+
}
|
|
274
|
+
function splitCode(fileLines) {
|
|
275
|
+
const sections = [];
|
|
276
|
+
let current = [];
|
|
277
|
+
for (let i = 0; i < fileLines.length; i++) {
|
|
278
|
+
const line = fileLines[i];
|
|
279
|
+
const lineObj = { lineNumber: i + 1, text: line };
|
|
280
|
+
if (line.trim() === "" && current.length > 0) {
|
|
281
|
+
let hasNextNonBlank = false;
|
|
282
|
+
for (let j = i + 1; j < fileLines.length; j++) {
|
|
283
|
+
if (fileLines[j].trim() !== "") {
|
|
284
|
+
hasNextNonBlank = true;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (hasNextNonBlank) {
|
|
289
|
+
sections.push(current);
|
|
290
|
+
current = [lineObj];
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
current.push(lineObj);
|
|
295
|
+
}
|
|
296
|
+
if (current.length > 0) {
|
|
297
|
+
sections.push(current);
|
|
298
|
+
}
|
|
299
|
+
return sections;
|
|
300
|
+
}
|
|
301
|
+
function splitParagraphs(fileLines) {
|
|
302
|
+
const sections = [];
|
|
303
|
+
let current = [];
|
|
304
|
+
for (let i = 0; i < fileLines.length; i++) {
|
|
305
|
+
const line = fileLines[i];
|
|
306
|
+
const lineObj = { lineNumber: i + 1, text: line };
|
|
307
|
+
if (line.trim() === "" && current.length > 0) {
|
|
308
|
+
sections.push(current);
|
|
309
|
+
current = [lineObj];
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
current.push(lineObj);
|
|
313
|
+
}
|
|
314
|
+
if (current.length > 0) {
|
|
315
|
+
sections.push(current);
|
|
316
|
+
}
|
|
317
|
+
return sections;
|
|
318
|
+
}
|
|
319
|
+
function sectionText(section) {
|
|
320
|
+
return section.map((l) => l.text).join("\n");
|
|
321
|
+
}
|
|
322
|
+
function chunkFile(filePath, content, options) {
|
|
323
|
+
if (content.trim() === "") return [];
|
|
324
|
+
if (isBinary(content)) return [];
|
|
325
|
+
const targetSize = options?.targetSize ?? DEFAULT_TARGET_SIZE;
|
|
326
|
+
const overlapSize = options?.overlapSize ?? DEFAULT_OVERLAP_SIZE;
|
|
327
|
+
const fileLines = content.split("\n");
|
|
328
|
+
const ext = extname(filePath).toLowerCase();
|
|
329
|
+
const sections = splitIntoSections(fileLines, ext);
|
|
330
|
+
const chunks = [];
|
|
331
|
+
let accumulated = [];
|
|
332
|
+
let accumulatedLength = 0;
|
|
333
|
+
function emitChunk(lines, overlapLines2) {
|
|
334
|
+
if (lines.length === 0) return [];
|
|
335
|
+
const allLines = [...overlapLines2, ...lines];
|
|
336
|
+
const text = allLines.map((l) => l.text).join("\n");
|
|
337
|
+
const startLine = allLines[0].lineNumber;
|
|
338
|
+
const endLine = allLines[allLines.length - 1].lineNumber;
|
|
339
|
+
chunks.push({
|
|
340
|
+
id: generateChunkId(filePath, startLine, endLine),
|
|
341
|
+
filePath,
|
|
342
|
+
startLine,
|
|
343
|
+
endLine,
|
|
344
|
+
text,
|
|
345
|
+
contentHash: chunkContentHash(text)
|
|
346
|
+
});
|
|
347
|
+
if (overlapSize <= 0) return [];
|
|
348
|
+
const overlapResult = [];
|
|
349
|
+
let overlapLen = 0;
|
|
350
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
351
|
+
const lineLen = lines[i].text.length + 1;
|
|
352
|
+
if (overlapLen + lineLen > overlapSize && overlapResult.length > 0) break;
|
|
353
|
+
overlapResult.unshift(lines[i]);
|
|
354
|
+
overlapLen += lineLen;
|
|
355
|
+
}
|
|
356
|
+
return overlapResult;
|
|
357
|
+
}
|
|
358
|
+
let overlapLines = [];
|
|
359
|
+
for (const section of sections) {
|
|
360
|
+
const sectionLen = sectionText(section).length;
|
|
361
|
+
if (accumulatedLength > 0 && accumulatedLength + sectionLen > targetSize) {
|
|
362
|
+
overlapLines = emitChunk(accumulated, overlapLines);
|
|
363
|
+
accumulated = [];
|
|
364
|
+
accumulatedLength = 0;
|
|
365
|
+
}
|
|
366
|
+
accumulated.push(...section);
|
|
367
|
+
accumulatedLength += sectionLen;
|
|
368
|
+
if (accumulatedLength > targetSize) {
|
|
369
|
+
overlapLines = emitChunk(accumulated, overlapLines);
|
|
370
|
+
accumulated = [];
|
|
371
|
+
accumulatedLength = 0;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (accumulated.length > 0) {
|
|
375
|
+
emitChunk(accumulated, overlapLines);
|
|
376
|
+
}
|
|
377
|
+
return chunks;
|
|
378
|
+
}
|
|
379
|
+
var DEFAULT_TARGET_SIZE, DEFAULT_OVERLAP_SIZE;
|
|
380
|
+
var init_chunking = __esm({
|
|
381
|
+
"src/memory/knowledge/chunking.ts"() {
|
|
382
|
+
init_types();
|
|
383
|
+
DEFAULT_TARGET_SIZE = 1600;
|
|
384
|
+
DEFAULT_OVERLAP_SIZE = 320;
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
function fileHash(content) {
|
|
388
|
+
return createHash("sha256").update(content).digest("hex");
|
|
389
|
+
}
|
|
390
|
+
function fileHashKey(relativePath) {
|
|
391
|
+
return "file_hash:" + relativePath;
|
|
392
|
+
}
|
|
393
|
+
function getStoredFileHash(repoRoot, relativePath) {
|
|
394
|
+
const db = openKnowledgeDb(repoRoot);
|
|
395
|
+
const row = db.prepare("SELECT value FROM metadata WHERE key = ?").get(fileHashKey(relativePath));
|
|
396
|
+
return row?.value ?? null;
|
|
397
|
+
}
|
|
398
|
+
function setFileHash(repoRoot, relativePath, hash) {
|
|
399
|
+
const db = openKnowledgeDb(repoRoot);
|
|
400
|
+
db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run(fileHashKey(relativePath), hash);
|
|
401
|
+
}
|
|
402
|
+
function removeFileHash(repoRoot, relativePath) {
|
|
403
|
+
const db = openKnowledgeDb(repoRoot);
|
|
404
|
+
db.prepare("DELETE FROM metadata WHERE key = ?").run(fileHashKey(relativePath));
|
|
405
|
+
}
|
|
406
|
+
async function walkSupportedFiles(baseDir, repoRoot) {
|
|
407
|
+
const results = [];
|
|
408
|
+
let entries;
|
|
409
|
+
try {
|
|
410
|
+
entries = await readdir(baseDir, { recursive: true, withFileTypes: true });
|
|
411
|
+
} catch {
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
if (!entry.isFile()) continue;
|
|
416
|
+
const ext = extname(entry.name).toLowerCase();
|
|
417
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
418
|
+
const fullPath = join(entry.parentPath ?? entry.path, entry.name);
|
|
419
|
+
const relPath = relative(repoRoot, fullPath);
|
|
420
|
+
results.push(relPath);
|
|
421
|
+
}
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
424
|
+
async function indexDocs(repoRoot, options = {}) {
|
|
425
|
+
const start = Date.now();
|
|
426
|
+
const docsDir = options.docsDir ?? "docs";
|
|
427
|
+
const force = options.force ?? false;
|
|
428
|
+
const stats = {
|
|
429
|
+
filesIndexed: 0,
|
|
430
|
+
filesSkipped: 0,
|
|
431
|
+
filesErrored: 0,
|
|
432
|
+
chunksCreated: 0,
|
|
433
|
+
chunksDeleted: 0,
|
|
434
|
+
durationMs: 0
|
|
435
|
+
};
|
|
436
|
+
const docsPath = join(repoRoot, docsDir);
|
|
437
|
+
const filePaths = await walkSupportedFiles(docsPath, repoRoot);
|
|
438
|
+
for (const relPath of filePaths) {
|
|
439
|
+
const fullPath = join(repoRoot, relPath);
|
|
440
|
+
let content;
|
|
441
|
+
try {
|
|
442
|
+
content = await readFile(fullPath, "utf-8");
|
|
443
|
+
} catch {
|
|
444
|
+
stats.filesErrored++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const hash = fileHash(content);
|
|
448
|
+
const storedHash = getStoredFileHash(repoRoot, relPath);
|
|
449
|
+
if (!force && storedHash === hash) {
|
|
450
|
+
stats.filesSkipped++;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const chunks = chunkFile(relPath, content);
|
|
454
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
455
|
+
const knowledgeChunks = chunks.map((chunk) => ({
|
|
456
|
+
id: chunk.id,
|
|
457
|
+
filePath: chunk.filePath,
|
|
458
|
+
startLine: chunk.startLine,
|
|
459
|
+
endLine: chunk.endLine,
|
|
460
|
+
contentHash: chunk.contentHash,
|
|
461
|
+
text: chunk.text,
|
|
462
|
+
updatedAt: now
|
|
463
|
+
}));
|
|
464
|
+
const db = openKnowledgeDb(repoRoot);
|
|
465
|
+
db.transaction(() => {
|
|
466
|
+
deleteChunksByFilePath(repoRoot, [relPath]);
|
|
467
|
+
if (knowledgeChunks.length > 0) {
|
|
468
|
+
upsertChunks(repoRoot, knowledgeChunks);
|
|
469
|
+
}
|
|
470
|
+
setFileHash(repoRoot, relPath, hash);
|
|
471
|
+
})();
|
|
472
|
+
stats.filesIndexed++;
|
|
473
|
+
stats.chunksCreated += knowledgeChunks.length;
|
|
474
|
+
}
|
|
475
|
+
const indexedPaths = getIndexedFilePaths(repoRoot);
|
|
476
|
+
const currentPathSet = new Set(filePaths);
|
|
477
|
+
const stalePaths = indexedPaths.filter((p) => !currentPathSet.has(p));
|
|
478
|
+
if (stalePaths.length > 0) {
|
|
479
|
+
for (const path2 of stalePaths) {
|
|
480
|
+
stats.chunksDeleted += getChunkCountByFilePath(repoRoot, path2);
|
|
481
|
+
}
|
|
482
|
+
deleteChunksByFilePath(repoRoot, stalePaths);
|
|
483
|
+
for (const path2 of stalePaths) {
|
|
484
|
+
removeFileHash(repoRoot, path2);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
setLastIndexTime(repoRoot, (/* @__PURE__ */ new Date()).toISOString());
|
|
488
|
+
stats.durationMs = Date.now() - start;
|
|
489
|
+
return stats;
|
|
490
|
+
}
|
|
491
|
+
var init_indexing = __esm({
|
|
492
|
+
"src/memory/knowledge/indexing.ts"() {
|
|
493
|
+
init_connection();
|
|
494
|
+
init_sync();
|
|
495
|
+
init_chunking();
|
|
496
|
+
init_types();
|
|
497
|
+
}
|
|
498
|
+
});
|
|
13
499
|
var _require = createRequire(import.meta.url);
|
|
14
500
|
var _pkg = _require("../package.json");
|
|
15
501
|
var VERSION = _pkg.version;
|
|
@@ -224,29 +710,9 @@ async function readLessons(repoRoot, options = {}) {
|
|
|
224
710
|
const lessons = result.items.filter((item) => item.type === "lesson");
|
|
225
711
|
return { lessons, skippedCount: result.skippedCount };
|
|
226
712
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
function ensureSqliteAvailable() {
|
|
231
|
-
if (checked) return;
|
|
232
|
-
try {
|
|
233
|
-
const module = require2("better-sqlite3");
|
|
234
|
-
const Constructor = module.default || module;
|
|
235
|
-
const testDb = new Constructor(":memory:");
|
|
236
|
-
testDb.close();
|
|
237
|
-
DatabaseConstructor = Constructor;
|
|
238
|
-
checked = true;
|
|
239
|
-
} catch (cause) {
|
|
240
|
-
throw new Error(
|
|
241
|
-
'better-sqlite3 failed to load.\nRun: npx ca setup (auto-configures pnpm native builds)\nOr manually add to your package.json:\n "pnpm": { "onlyBuiltDependencies": ["better-sqlite3", "node-llama-cpp"] }\nThen run: pnpm install && pnpm rebuild better-sqlite3\nFor npm/yarn, run: npm rebuild better-sqlite3',
|
|
242
|
-
{ cause }
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function getDatabaseConstructor() {
|
|
247
|
-
ensureSqliteAvailable();
|
|
248
|
-
return DatabaseConstructor;
|
|
249
|
-
}
|
|
713
|
+
|
|
714
|
+
// src/memory/storage/sqlite/connection.ts
|
|
715
|
+
init_availability();
|
|
250
716
|
|
|
251
717
|
// src/memory/storage/sqlite/schema.ts
|
|
252
718
|
var SCHEMA_VERSION = 3;
|
|
@@ -342,7 +808,10 @@ function openDb(repoRoot, options = {}) {
|
|
|
342
808
|
database = new Database(key);
|
|
343
809
|
if (!hasExpectedVersion(database)) {
|
|
344
810
|
database.close();
|
|
345
|
-
|
|
811
|
+
try {
|
|
812
|
+
unlinkSync(key);
|
|
813
|
+
} catch {
|
|
814
|
+
}
|
|
346
815
|
database = new Database(key);
|
|
347
816
|
}
|
|
348
817
|
database.pragma("journal_mode = WAL");
|
|
@@ -483,7 +952,65 @@ async function syncIfNeeded(repoRoot, options = {}) {
|
|
|
483
952
|
return false;
|
|
484
953
|
}
|
|
485
954
|
|
|
955
|
+
// src/memory/search/hybrid.ts
|
|
956
|
+
var DEFAULT_VECTOR_WEIGHT = 0.7;
|
|
957
|
+
var DEFAULT_TEXT_WEIGHT = 0.3;
|
|
958
|
+
var CANDIDATE_MULTIPLIER = 4;
|
|
959
|
+
var MIN_HYBRID_SCORE = 0.35;
|
|
960
|
+
function normalizeBm25Rank(rank) {
|
|
961
|
+
if (!Number.isFinite(rank)) return 0;
|
|
962
|
+
const abs = Math.abs(rank);
|
|
963
|
+
return abs / (1 + abs);
|
|
964
|
+
}
|
|
965
|
+
function mergeHybridScores(vectorResults, keywordResults, getId, options) {
|
|
966
|
+
if (vectorResults.length === 0 && keywordResults.length === 0) return [];
|
|
967
|
+
const rawVecW = options?.vectorWeight ?? DEFAULT_VECTOR_WEIGHT;
|
|
968
|
+
const rawTxtW = options?.textWeight ?? DEFAULT_TEXT_WEIGHT;
|
|
969
|
+
const total = rawVecW + rawTxtW;
|
|
970
|
+
if (total <= 0) return [];
|
|
971
|
+
const vecW = rawVecW / total;
|
|
972
|
+
const txtW = rawTxtW / total;
|
|
973
|
+
const limit = options?.limit;
|
|
974
|
+
const minScore = options?.minScore;
|
|
975
|
+
const merged = /* @__PURE__ */ new Map();
|
|
976
|
+
for (const v of vectorResults) {
|
|
977
|
+
merged.set(getId(v.item), { item: v.item, vecScore: v.score, txtScore: 0 });
|
|
978
|
+
}
|
|
979
|
+
for (const k of keywordResults) {
|
|
980
|
+
const id = getId(k.item);
|
|
981
|
+
const existing = merged.get(id);
|
|
982
|
+
if (existing) {
|
|
983
|
+
existing.txtScore = k.score;
|
|
984
|
+
} else {
|
|
985
|
+
merged.set(id, { item: k.item, vecScore: 0, txtScore: k.score });
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const results = [];
|
|
989
|
+
for (const entry of merged.values()) {
|
|
990
|
+
results.push({
|
|
991
|
+
item: entry.item,
|
|
992
|
+
score: vecW * entry.vecScore + txtW * entry.txtScore
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
results.sort((a, b) => b.score - a.score);
|
|
996
|
+
const filtered = minScore !== void 0 ? results.filter((r) => r.score >= minScore) : results;
|
|
997
|
+
return limit !== void 0 ? filtered.slice(0, limit) : filtered;
|
|
998
|
+
}
|
|
999
|
+
function mergeHybridResults(vectorResults, keywordResults, options) {
|
|
1000
|
+
const genericVec = vectorResults.map((v) => ({ item: v.lesson, score: v.score }));
|
|
1001
|
+
const genericKw = keywordResults.map((k) => ({ item: k.lesson, score: k.score }));
|
|
1002
|
+
const merged = mergeHybridScores(genericVec, genericKw, (item) => item.id, options);
|
|
1003
|
+
return merged.map((m) => ({ lesson: m.item, score: m.score }));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
486
1006
|
// src/memory/storage/sqlite/search.ts
|
|
1007
|
+
function safeJsonParse(value, fallback) {
|
|
1008
|
+
try {
|
|
1009
|
+
return JSON.parse(value);
|
|
1010
|
+
} catch {
|
|
1011
|
+
return fallback;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
487
1014
|
function rowToMemoryItem(row) {
|
|
488
1015
|
const item = {
|
|
489
1016
|
id: row.id,
|
|
@@ -492,9 +1019,9 @@ function rowToMemoryItem(row) {
|
|
|
492
1019
|
insight: row.insight,
|
|
493
1020
|
tags: row.tags ? row.tags.split(",").filter(Boolean) : [],
|
|
494
1021
|
source: row.source,
|
|
495
|
-
context:
|
|
496
|
-
supersedes:
|
|
497
|
-
related:
|
|
1022
|
+
context: safeJsonParse(row.context, {}),
|
|
1023
|
+
supersedes: safeJsonParse(row.supersedes, []),
|
|
1024
|
+
related: safeJsonParse(row.related, []),
|
|
498
1025
|
created: row.created,
|
|
499
1026
|
confirmed: row.confirmed === 1
|
|
500
1027
|
};
|
|
@@ -525,7 +1052,7 @@ function rowToMemoryItem(row) {
|
|
|
525
1052
|
}
|
|
526
1053
|
var FTS_OPERATORS = /* @__PURE__ */ new Set(["AND", "OR", "NOT", "NEAR"]);
|
|
527
1054
|
function sanitizeFtsQuery(query) {
|
|
528
|
-
const stripped = query.replace(/["
|
|
1055
|
+
const stripped = query.replace(/["*^+\-():{}]/g, "");
|
|
529
1056
|
const tokens = stripped.split(/\s+/).filter((t) => t.length > 0 && !FTS_OPERATORS.has(t));
|
|
530
1057
|
return tokens.join(" ");
|
|
531
1058
|
}
|
|
@@ -546,44 +1073,47 @@ function incrementRetrievalCount(repoRoot, lessonIds) {
|
|
|
546
1073
|
});
|
|
547
1074
|
updateMany(lessonIds);
|
|
548
1075
|
}
|
|
549
|
-
|
|
1076
|
+
function executeFtsQuery(repoRoot, query, limit, options) {
|
|
550
1077
|
const database = openDb(repoRoot);
|
|
551
|
-
const countResult = database.prepare("SELECT COUNT(*) as cnt FROM lessons").get();
|
|
552
|
-
if (countResult.cnt === 0) return [];
|
|
553
1078
|
const sanitized = sanitizeFtsQuery(query);
|
|
554
1079
|
if (sanitized === "") return [];
|
|
1080
|
+
const selectCols = options.includeRank ? "l.*, fts.rank" : "l.*";
|
|
1081
|
+
const orderClause = options.includeRank ? "ORDER BY fts.rank" : "";
|
|
1082
|
+
const typeClause = options.typeFilter ? "AND l.type = ?" : "";
|
|
1083
|
+
const sql = `
|
|
1084
|
+
SELECT ${selectCols}
|
|
1085
|
+
FROM lessons l
|
|
1086
|
+
JOIN lessons_fts fts ON l.rowid = fts.rowid
|
|
1087
|
+
WHERE lessons_fts MATCH ?
|
|
1088
|
+
AND l.invalidated_at IS NULL
|
|
1089
|
+
${typeClause}
|
|
1090
|
+
${orderClause}
|
|
1091
|
+
LIMIT ?
|
|
1092
|
+
`;
|
|
1093
|
+
const params = options.typeFilter ? [sanitized, options.typeFilter, limit] : [sanitized, limit];
|
|
555
1094
|
try {
|
|
556
|
-
|
|
557
|
-
const rows2 = database.prepare(
|
|
558
|
-
`
|
|
559
|
-
SELECT l.*
|
|
560
|
-
FROM lessons l
|
|
561
|
-
JOIN lessons_fts fts ON l.rowid = fts.rowid
|
|
562
|
-
WHERE lessons_fts MATCH ?
|
|
563
|
-
AND l.invalidated_at IS NULL
|
|
564
|
-
AND l.type = ?
|
|
565
|
-
LIMIT ?
|
|
566
|
-
`
|
|
567
|
-
).all(sanitized, typeFilter, limit);
|
|
568
|
-
return rows2.map(rowToMemoryItem).filter((x) => x !== null);
|
|
569
|
-
}
|
|
570
|
-
const rows = database.prepare(
|
|
571
|
-
`
|
|
572
|
-
SELECT l.*
|
|
573
|
-
FROM lessons l
|
|
574
|
-
JOIN lessons_fts fts ON l.rowid = fts.rowid
|
|
575
|
-
WHERE lessons_fts MATCH ?
|
|
576
|
-
AND l.invalidated_at IS NULL
|
|
577
|
-
LIMIT ?
|
|
578
|
-
`
|
|
579
|
-
).all(sanitized, limit);
|
|
580
|
-
return rows.map(rowToMemoryItem).filter((x) => x !== null);
|
|
1095
|
+
return database.prepare(sql).all(...params);
|
|
581
1096
|
} catch (err) {
|
|
582
1097
|
const message = err instanceof Error ? err.message : "Unknown FTS5 error";
|
|
583
1098
|
console.error(`[compound-agent] search error: ${message}`);
|
|
584
1099
|
return [];
|
|
585
1100
|
}
|
|
586
1101
|
}
|
|
1102
|
+
async function searchKeyword(repoRoot, query, limit, typeFilter) {
|
|
1103
|
+
const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: false, typeFilter });
|
|
1104
|
+
return rows.map(rowToMemoryItem).filter((x) => x !== null);
|
|
1105
|
+
}
|
|
1106
|
+
async function searchKeywordScored(repoRoot, query, limit, typeFilter) {
|
|
1107
|
+
const rows = executeFtsQuery(repoRoot, query, limit, { includeRank: true, typeFilter });
|
|
1108
|
+
const results = [];
|
|
1109
|
+
for (const row of rows) {
|
|
1110
|
+
const lesson = rowToMemoryItem(row);
|
|
1111
|
+
if (lesson) {
|
|
1112
|
+
results.push({ lesson, score: normalizeBm25Rank(row.rank) });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return results;
|
|
1116
|
+
}
|
|
587
1117
|
|
|
588
1118
|
// src/utils.ts
|
|
589
1119
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -1161,8 +1691,13 @@ async function loadSessionLessons(repoRoot, limit = DEFAULT_LIMIT2) {
|
|
|
1161
1691
|
// src/memory/retrieval/plan.ts
|
|
1162
1692
|
var DEFAULT_LIMIT3 = 5;
|
|
1163
1693
|
async function retrieveForPlan(repoRoot, planText, limit = DEFAULT_LIMIT3) {
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1694
|
+
const candidateLimit = limit * CANDIDATE_MULTIPLIER;
|
|
1695
|
+
const [vectorResults, keywordResults] = await Promise.all([
|
|
1696
|
+
searchVector(repoRoot, planText, { limit: candidateLimit }),
|
|
1697
|
+
searchKeywordScored(repoRoot, planText, candidateLimit)
|
|
1698
|
+
]);
|
|
1699
|
+
const merged = mergeHybridResults(vectorResults, keywordResults, { minScore: MIN_HYBRID_SCORE });
|
|
1700
|
+
const ranked = rankLessons(merged);
|
|
1166
1701
|
const topLessons = ranked.slice(0, limit);
|
|
1167
1702
|
if (topLessons.length > 0) {
|
|
1168
1703
|
incrementRetrievalCount(repoRoot, topLessons.map((item) => item.lesson.id));
|
|
@@ -1185,12 +1720,173 @@ No relevant lessons found for this plan.`;
|
|
|
1185
1720
|
${lessonLines.join("\n")}`;
|
|
1186
1721
|
}
|
|
1187
1722
|
|
|
1723
|
+
// src/memory/storage/sqlite-knowledge/index.ts
|
|
1724
|
+
init_connection();
|
|
1725
|
+
init_schema();
|
|
1726
|
+
|
|
1727
|
+
// src/memory/storage/sqlite-knowledge/cache.ts
|
|
1728
|
+
init_connection();
|
|
1729
|
+
init_types();
|
|
1730
|
+
function getCachedChunkEmbedding(repoRoot, chunkId, expectedHash) {
|
|
1731
|
+
const database = openKnowledgeDb(repoRoot);
|
|
1732
|
+
const row = database.prepare("SELECT embedding, content_hash FROM chunks WHERE id = ?").get(chunkId);
|
|
1733
|
+
if (!row || !row.embedding || !row.content_hash) {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
if (expectedHash && row.content_hash !== expectedHash) {
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
return new Float32Array(
|
|
1740
|
+
row.embedding.buffer,
|
|
1741
|
+
row.embedding.byteOffset,
|
|
1742
|
+
row.embedding.byteLength / 4
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
function setCachedChunkEmbedding(repoRoot, chunkId, embedding, hash) {
|
|
1746
|
+
const database = openKnowledgeDb(repoRoot);
|
|
1747
|
+
const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
|
|
1748
|
+
const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
|
|
1749
|
+
database.prepare("UPDATE chunks SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, chunkId);
|
|
1750
|
+
}
|
|
1751
|
+
function collectCachedChunkEmbeddings(database) {
|
|
1752
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1753
|
+
const rows = database.prepare("SELECT id, embedding, content_hash FROM chunks WHERE embedding IS NOT NULL").all();
|
|
1754
|
+
for (const row of rows) {
|
|
1755
|
+
if (row.embedding && row.content_hash) {
|
|
1756
|
+
cache.set(row.id, { embedding: row.embedding, contentHash: row.content_hash });
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return cache;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/memory/storage/sqlite-knowledge/search.ts
|
|
1763
|
+
init_connection();
|
|
1764
|
+
function rowToChunk(row) {
|
|
1765
|
+
const chunk = {
|
|
1766
|
+
id: row.id,
|
|
1767
|
+
filePath: row.file_path,
|
|
1768
|
+
startLine: row.start_line,
|
|
1769
|
+
endLine: row.end_line,
|
|
1770
|
+
contentHash: row.content_hash,
|
|
1771
|
+
text: row.text,
|
|
1772
|
+
updatedAt: row.updated_at
|
|
1773
|
+
};
|
|
1774
|
+
if (row.model !== null) {
|
|
1775
|
+
chunk.model = row.model;
|
|
1776
|
+
}
|
|
1777
|
+
return chunk;
|
|
1778
|
+
}
|
|
1779
|
+
function searchChunksKeywordScored(repoRoot, query, limit) {
|
|
1780
|
+
const database = openKnowledgeDb(repoRoot);
|
|
1781
|
+
const sanitized = sanitizeFtsQuery(query);
|
|
1782
|
+
if (sanitized === "") return [];
|
|
1783
|
+
try {
|
|
1784
|
+
const rows = database.prepare(
|
|
1785
|
+
`SELECT c.*, fts.rank
|
|
1786
|
+
FROM chunks c
|
|
1787
|
+
JOIN chunks_fts fts ON c.rowid = fts.rowid
|
|
1788
|
+
WHERE chunks_fts MATCH ?
|
|
1789
|
+
ORDER BY fts.rank
|
|
1790
|
+
LIMIT ?`
|
|
1791
|
+
).all(sanitized, limit);
|
|
1792
|
+
return rows.map((row) => ({
|
|
1793
|
+
chunk: rowToChunk(row),
|
|
1794
|
+
score: normalizeBm25Rank(row.rank)
|
|
1795
|
+
}));
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
const message = err instanceof Error ? err.message : "Unknown FTS5 error";
|
|
1798
|
+
console.error(`[compound-agent] knowledge scored search error: ${message}`);
|
|
1799
|
+
return [];
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// src/memory/storage/sqlite-knowledge/index.ts
|
|
1804
|
+
init_sync();
|
|
1805
|
+
|
|
1806
|
+
// src/index.ts
|
|
1807
|
+
init_chunking();
|
|
1808
|
+
init_indexing();
|
|
1809
|
+
|
|
1810
|
+
// src/memory/knowledge/search.ts
|
|
1811
|
+
init_connection();
|
|
1812
|
+
var DEFAULT_KNOWLEDGE_LIMIT = 6;
|
|
1813
|
+
async function searchKnowledgeVector(repoRoot, query, options) {
|
|
1814
|
+
const limit = options?.limit ?? DEFAULT_KNOWLEDGE_LIMIT;
|
|
1815
|
+
const database = openKnowledgeDb(repoRoot);
|
|
1816
|
+
const embRows = database.prepare("SELECT id, embedding FROM chunks WHERE embedding IS NOT NULL").all();
|
|
1817
|
+
if (embRows.length === 0) return [];
|
|
1818
|
+
const queryVector = await embedText(query);
|
|
1819
|
+
const scored = [];
|
|
1820
|
+
for (const row of embRows) {
|
|
1821
|
+
const embFloat = new Float32Array(
|
|
1822
|
+
row.embedding.buffer,
|
|
1823
|
+
row.embedding.byteOffset,
|
|
1824
|
+
row.embedding.byteLength / 4
|
|
1825
|
+
);
|
|
1826
|
+
scored.push({ id: row.id, score: cosineSimilarity(queryVector, embFloat) });
|
|
1827
|
+
}
|
|
1828
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1829
|
+
const topK = scored.slice(0, limit);
|
|
1830
|
+
if (topK.length === 0) return [];
|
|
1831
|
+
const placeholders = topK.map(() => "?").join(",");
|
|
1832
|
+
const sql = `SELECT id, file_path, start_line, end_line, content_hash, text, model, updated_at FROM chunks WHERE id IN (${placeholders})`;
|
|
1833
|
+
const dataRows = database.prepare(sql).all(...topK.map((r) => r.id));
|
|
1834
|
+
const dataMap = new Map(dataRows.map((r) => [r.id, r]));
|
|
1835
|
+
const results = [];
|
|
1836
|
+
for (const { id, score } of topK) {
|
|
1837
|
+
const row = dataMap.get(id);
|
|
1838
|
+
if (!row) continue;
|
|
1839
|
+
const chunk = {
|
|
1840
|
+
id: row.id,
|
|
1841
|
+
filePath: row.file_path,
|
|
1842
|
+
startLine: row.start_line,
|
|
1843
|
+
endLine: row.end_line,
|
|
1844
|
+
contentHash: row.content_hash,
|
|
1845
|
+
text: row.text,
|
|
1846
|
+
updatedAt: row.updated_at
|
|
1847
|
+
};
|
|
1848
|
+
if (row.model !== null) {
|
|
1849
|
+
chunk.model = row.model;
|
|
1850
|
+
}
|
|
1851
|
+
results.push({ item: chunk, score });
|
|
1852
|
+
}
|
|
1853
|
+
return results;
|
|
1854
|
+
}
|
|
1855
|
+
async function searchKnowledge(repoRoot, query, options) {
|
|
1856
|
+
const limit = options?.limit ?? DEFAULT_KNOWLEDGE_LIMIT;
|
|
1857
|
+
const candidateLimit = limit * CANDIDATE_MULTIPLIER;
|
|
1858
|
+
const usability = await isModelUsable();
|
|
1859
|
+
if (usability.usable) {
|
|
1860
|
+
const [vectorResults, keywordResults2] = await Promise.all([
|
|
1861
|
+
searchKnowledgeVector(repoRoot, query, { limit: candidateLimit }),
|
|
1862
|
+
Promise.resolve(searchChunksKeywordScored(repoRoot, query, candidateLimit))
|
|
1863
|
+
]);
|
|
1864
|
+
if (vectorResults.length === 0) {
|
|
1865
|
+
return keywordResults2.map((k) => ({ item: k.chunk, score: k.score })).slice(0, limit);
|
|
1866
|
+
}
|
|
1867
|
+
const genericKw = keywordResults2.map((k) => ({
|
|
1868
|
+
item: k.chunk,
|
|
1869
|
+
score: k.score
|
|
1870
|
+
}));
|
|
1871
|
+
const merged = mergeHybridScores(
|
|
1872
|
+
vectorResults,
|
|
1873
|
+
genericKw,
|
|
1874
|
+
(item) => item.id,
|
|
1875
|
+
{ limit, minScore: MIN_HYBRID_SCORE }
|
|
1876
|
+
);
|
|
1877
|
+
return merged;
|
|
1878
|
+
}
|
|
1879
|
+
const keywordResults = searchChunksKeywordScored(repoRoot, query, limit);
|
|
1880
|
+
return keywordResults.map((k) => ({ item: k.chunk, score: k.score }));
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1188
1883
|
// src/cli-utils.ts
|
|
1189
1884
|
function getRepoRoot() {
|
|
1190
|
-
return process.env["COMPOUND_AGENT_ROOT"]
|
|
1885
|
+
return process.env["COMPOUND_AGENT_ROOT"] || process.cwd();
|
|
1191
1886
|
}
|
|
1192
1887
|
var STATE_DIR = ".claude";
|
|
1193
1888
|
var STATE_FILE = ".ca-phase-state.json";
|
|
1889
|
+
var PHASE_STATE_MAX_AGE_MS = 72 * 60 * 60 * 1e3;
|
|
1194
1890
|
var PHASES = ["brainstorm", "plan", "work", "review", "compound"];
|
|
1195
1891
|
var GATES = ["post-plan", "gate-3", "gate-4", "final"];
|
|
1196
1892
|
function getStatePath(repoRoot) {
|
|
@@ -1220,11 +1916,24 @@ function getPhaseState(repoRoot) {
|
|
|
1220
1916
|
if (!existsSync(path2)) return null;
|
|
1221
1917
|
const raw = readFileSync(path2, "utf-8");
|
|
1222
1918
|
const parsed = JSON.parse(raw);
|
|
1223
|
-
|
|
1919
|
+
if (!validatePhaseState(parsed)) return null;
|
|
1920
|
+
const age = Date.now() - new Date(parsed.started_at).getTime();
|
|
1921
|
+
if (age > PHASE_STATE_MAX_AGE_MS) {
|
|
1922
|
+
cleanPhaseState(repoRoot);
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
return parsed;
|
|
1224
1926
|
} catch {
|
|
1225
1927
|
return null;
|
|
1226
1928
|
}
|
|
1227
1929
|
}
|
|
1930
|
+
function cleanPhaseState(repoRoot) {
|
|
1931
|
+
try {
|
|
1932
|
+
const path2 = getStatePath(repoRoot);
|
|
1933
|
+
if (existsSync(path2)) unlinkSync(path2);
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1228
1937
|
|
|
1229
1938
|
// src/commands/management-prime.ts
|
|
1230
1939
|
var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
|
|
@@ -1238,6 +1947,7 @@ var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
|
|
|
1238
1947
|
| Command | Purpose |
|
|
1239
1948
|
|---------|---------|
|
|
1240
1949
|
| \`npx ca search "query"\` | Search lessons - call BEFORE architectural decisions |
|
|
1950
|
+
| \`npx ca knowledge "query"\` | Search docs knowledge - use for architecture context |
|
|
1241
1951
|
| \`npx ca learn "insight"\` | Capture lessons - call AFTER corrections or discoveries |
|
|
1242
1952
|
|
|
1243
1953
|
## Core Constraints
|
|
@@ -1250,7 +1960,7 @@ var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
|
|
|
1250
1960
|
|
|
1251
1961
|
## Retrieval Protocol
|
|
1252
1962
|
|
|
1253
|
-
You MUST call \`npx ca search\` BEFORE:
|
|
1963
|
+
You MUST call \`npx ca search\` and \`npx ca knowledge\` BEFORE:
|
|
1254
1964
|
- Architectural decisions or complex planning
|
|
1255
1965
|
- Implementing patterns you've done before in this repo
|
|
1256
1966
|
|
|
@@ -1629,6 +2339,11 @@ var AuditReportSchema = z.object({
|
|
|
1629
2339
|
timestamp: z.string()
|
|
1630
2340
|
});
|
|
1631
2341
|
|
|
1632
|
-
|
|
2342
|
+
// src/memory/knowledge/index.ts
|
|
2343
|
+
init_chunking();
|
|
2344
|
+
init_types();
|
|
2345
|
+
init_indexing();
|
|
2346
|
+
|
|
2347
|
+
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 };
|
|
1633
2348
|
//# sourceMappingURL=index.js.map
|
|
1634
2349
|
//# sourceMappingURL=index.js.map
|