compound-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js ADDED
@@ -0,0 +1,1025 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { mkdir, appendFile, readFile } from 'fs/promises';
6
+ import { join, dirname } from 'path';
7
+ import { createHash } from 'crypto';
8
+ import { mkdirSync, unlinkSync } from 'fs';
9
+ import { createRequire } from 'module';
10
+ import { getLlama, resolveModelFile } from 'node-llama-cpp';
11
+ import { homedir } from 'os';
12
+ import 'chalk';
13
+ import 'child_process';
14
+
15
+ // src/cli-utils.ts
16
+ function getRepoRoot() {
17
+ return process.env["COMPOUND_AGENT_ROOT"] ?? process.cwd();
18
+ }
19
+ var SourceSchema = z.enum([
20
+ "user_correction",
21
+ "self_correction",
22
+ "test_failure",
23
+ "manual"
24
+ ]);
25
+ var ContextSchema = z.object({
26
+ tool: z.string(),
27
+ intent: z.string()
28
+ });
29
+ var PatternSchema = z.object({
30
+ bad: z.string(),
31
+ good: z.string()
32
+ });
33
+ var CitationSchema = z.object({
34
+ file: z.string().min(1),
35
+ // Source file path (required, non-empty)
36
+ line: z.number().int().positive().optional(),
37
+ // Line number (optional, must be positive)
38
+ commit: z.string().optional()
39
+ // Git commit hash (optional)
40
+ });
41
+ var SeveritySchema = z.enum(["high", "medium", "low"]);
42
+ var CompactionLevelSchema = z.union([
43
+ z.literal(0),
44
+ // Active
45
+ z.literal(1),
46
+ // Flagged (>90 days)
47
+ z.literal(2)
48
+ // Archived
49
+ ]);
50
+ var LessonTypeSchema = z.enum(["quick", "full"]);
51
+ var MemoryItemTypeSchema = z.enum(["lesson", "solution", "pattern", "preference"]);
52
+ var baseFields = {
53
+ // Core identity (required)
54
+ id: z.string(),
55
+ trigger: z.string(),
56
+ insight: z.string(),
57
+ // Metadata (required)
58
+ tags: z.array(z.string()),
59
+ source: SourceSchema,
60
+ context: ContextSchema,
61
+ created: z.string(),
62
+ // ISO8601
63
+ confirmed: z.boolean(),
64
+ // Relationships (required, can be empty arrays)
65
+ supersedes: z.array(z.string()),
66
+ related: z.array(z.string()),
67
+ // Extended fields (optional)
68
+ evidence: z.string().optional(),
69
+ severity: SeveritySchema.optional(),
70
+ // Lifecycle fields (optional)
71
+ deleted: z.boolean().optional(),
72
+ deletedAt: z.string().optional(),
73
+ retrievalCount: z.number().optional(),
74
+ // Provenance tracking (optional)
75
+ citation: CitationSchema.optional(),
76
+ // Age-based validity fields (optional)
77
+ compactionLevel: CompactionLevelSchema.optional(),
78
+ compactedAt: z.string().optional(),
79
+ lastRetrieved: z.string().optional(),
80
+ // Invalidation fields (optional)
81
+ invalidatedAt: z.string().optional(),
82
+ invalidationReason: z.string().optional()
83
+ };
84
+ var LessonItemSchema = z.object({
85
+ ...baseFields,
86
+ type: z.literal("lesson"),
87
+ pattern: PatternSchema.optional()
88
+ });
89
+ var SolutionItemSchema = z.object({
90
+ ...baseFields,
91
+ type: z.literal("solution"),
92
+ pattern: PatternSchema.optional()
93
+ });
94
+ var PatternItemSchema = z.object({
95
+ ...baseFields,
96
+ type: z.literal("pattern"),
97
+ pattern: PatternSchema
98
+ });
99
+ var PreferenceItemSchema = z.object({
100
+ ...baseFields,
101
+ type: z.literal("preference"),
102
+ pattern: PatternSchema.optional()
103
+ });
104
+ var MemoryItemSchema = z.discriminatedUnion("type", [
105
+ LessonItemSchema,
106
+ SolutionItemSchema,
107
+ PatternItemSchema,
108
+ PreferenceItemSchema
109
+ ]);
110
+ var LegacyLessonSchema = z.object({
111
+ ...baseFields,
112
+ type: LessonTypeSchema,
113
+ pattern: PatternSchema.optional()
114
+ });
115
+ var LegacyTombstoneSchema = z.object({
116
+ id: z.string(),
117
+ deleted: z.literal(true),
118
+ deletedAt: z.string()
119
+ // ISO8601
120
+ });
121
+ var LessonRecordSchema = z.union([
122
+ MemoryItemSchema,
123
+ LegacyLessonSchema,
124
+ LegacyTombstoneSchema
125
+ ]);
126
+ var MemoryItemRecordSchema = LessonRecordSchema;
127
+ var TYPE_PREFIXES = {
128
+ lesson: "L",
129
+ solution: "S",
130
+ pattern: "P",
131
+ preference: "R"
132
+ };
133
+ function generateId(insight, type) {
134
+ const prefix = TYPE_PREFIXES[type ?? "lesson"];
135
+ const hash = createHash("sha256").update(insight).digest("hex");
136
+ return `${prefix}${hash.slice(0, 8)}`;
137
+ }
138
+
139
+ // src/memory/storage/jsonl.ts
140
+ var LESSONS_PATH = ".claude/lessons/index.jsonl";
141
+ async function appendMemoryItem(repoRoot, item) {
142
+ const filePath = join(repoRoot, LESSONS_PATH);
143
+ await mkdir(dirname(filePath), { recursive: true });
144
+ const line = JSON.stringify(item) + "\n";
145
+ await appendFile(filePath, line, "utf-8");
146
+ }
147
+ function parseJsonLine(line, lineNumber, strict, onParseError) {
148
+ let parsed;
149
+ try {
150
+ parsed = JSON.parse(line);
151
+ } catch (err) {
152
+ const parseError = {
153
+ line: lineNumber,
154
+ message: `Invalid JSON: ${err.message}`,
155
+ cause: err
156
+ };
157
+ if (strict) {
158
+ throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
159
+ }
160
+ onParseError?.(parseError);
161
+ return null;
162
+ }
163
+ const result = MemoryItemRecordSchema.safeParse(parsed);
164
+ if (!result.success) {
165
+ const parseError = {
166
+ line: lineNumber,
167
+ message: `Schema validation failed: ${result.error.message}`,
168
+ cause: result.error
169
+ };
170
+ if (strict) {
171
+ throw new Error(`Parse error on line ${lineNumber}: ${parseError.message}`);
172
+ }
173
+ onParseError?.(parseError);
174
+ return null;
175
+ }
176
+ return result.data;
177
+ }
178
+ function toMemoryItem(record) {
179
+ if (record.deleted === true) {
180
+ return null;
181
+ }
182
+ if (record.type === "quick" || record.type === "full") {
183
+ return { ...record, type: "lesson" };
184
+ }
185
+ return record;
186
+ }
187
+ async function readMemoryItems(repoRoot, options = {}) {
188
+ const { strict = false, onParseError } = options;
189
+ const filePath = join(repoRoot, LESSONS_PATH);
190
+ let content;
191
+ try {
192
+ content = await readFile(filePath, "utf-8");
193
+ } catch (err) {
194
+ if (err.code === "ENOENT") {
195
+ return { items: [], skippedCount: 0 };
196
+ }
197
+ throw err;
198
+ }
199
+ const items = /* @__PURE__ */ new Map();
200
+ let skippedCount = 0;
201
+ const lines = content.split("\n");
202
+ for (let i = 0; i < lines.length; i++) {
203
+ const trimmed = lines[i].trim();
204
+ if (!trimmed) continue;
205
+ const record = parseJsonLine(trimmed, i + 1, strict, onParseError);
206
+ if (!record) {
207
+ skippedCount++;
208
+ continue;
209
+ }
210
+ if (record.deleted === true) {
211
+ items.delete(record.id);
212
+ } else {
213
+ const item = toMemoryItem(record);
214
+ if (item) {
215
+ items.set(record.id, item);
216
+ }
217
+ }
218
+ }
219
+ return { items: Array.from(items.values()), skippedCount };
220
+ }
221
+ var require2 = createRequire(import.meta.url);
222
+ var checked = false;
223
+ var DatabaseConstructor = null;
224
+ function ensureSqliteAvailable() {
225
+ if (checked) return;
226
+ try {
227
+ const module = require2("better-sqlite3");
228
+ const Constructor = module.default || module;
229
+ const testDb = new Constructor(":memory:");
230
+ testDb.close();
231
+ DatabaseConstructor = Constructor;
232
+ checked = true;
233
+ } catch (cause) {
234
+ throw new Error(
235
+ `better-sqlite3 failed to load.
236
+ If using pnpm, add to your project's package.json:
237
+ "pnpm": { "onlyBuiltDependencies": ["better-sqlite3"] }
238
+ Then run: pnpm install && pnpm rebuild better-sqlite3
239
+ For npm/yarn, run: npm rebuild better-sqlite3`,
240
+ { cause }
241
+ );
242
+ }
243
+ }
244
+ function getDatabaseConstructor() {
245
+ ensureSqliteAvailable();
246
+ return DatabaseConstructor;
247
+ }
248
+
249
+ // src/memory/storage/sqlite/schema.ts
250
+ var SCHEMA_VERSION = 3;
251
+ var SCHEMA_SQL = `
252
+ CREATE TABLE IF NOT EXISTS lessons (
253
+ id TEXT PRIMARY KEY,
254
+ type TEXT NOT NULL,
255
+ trigger TEXT NOT NULL,
256
+ insight TEXT NOT NULL,
257
+ evidence TEXT,
258
+ severity TEXT,
259
+ tags TEXT NOT NULL DEFAULT '',
260
+ source TEXT NOT NULL,
261
+ context TEXT NOT NULL DEFAULT '{}',
262
+ supersedes TEXT NOT NULL DEFAULT '[]',
263
+ related TEXT NOT NULL DEFAULT '[]',
264
+ created TEXT NOT NULL,
265
+ confirmed INTEGER NOT NULL DEFAULT 0,
266
+ deleted INTEGER NOT NULL DEFAULT 0,
267
+ retrieval_count INTEGER NOT NULL DEFAULT 0,
268
+ last_retrieved TEXT,
269
+ embedding BLOB,
270
+ content_hash TEXT,
271
+ invalidated_at TEXT,
272
+ invalidation_reason TEXT,
273
+ citation_file TEXT,
274
+ citation_line INTEGER,
275
+ citation_commit TEXT,
276
+ compaction_level INTEGER DEFAULT 0,
277
+ compacted_at TEXT,
278
+ pattern_bad TEXT,
279
+ pattern_good TEXT
280
+ );
281
+
282
+ CREATE VIRTUAL TABLE IF NOT EXISTS lessons_fts USING fts5(
283
+ id, trigger, insight, tags, pattern_bad, pattern_good,
284
+ content='lessons', content_rowid='rowid'
285
+ );
286
+
287
+ CREATE TRIGGER IF NOT EXISTS lessons_ai AFTER INSERT ON lessons BEGIN
288
+ INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
289
+ VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
290
+ END;
291
+
292
+ CREATE TRIGGER IF NOT EXISTS lessons_ad AFTER DELETE ON lessons BEGIN
293
+ INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
294
+ VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
295
+ END;
296
+
297
+ CREATE TRIGGER IF NOT EXISTS lessons_au AFTER UPDATE ON lessons BEGIN
298
+ INSERT INTO lessons_fts(lessons_fts, rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
299
+ VALUES ('delete', old.rowid, old.id, old.trigger, old.insight, old.tags, old.pattern_bad, old.pattern_good);
300
+ INSERT INTO lessons_fts(rowid, id, trigger, insight, tags, pattern_bad, pattern_good)
301
+ VALUES (new.rowid, new.id, new.trigger, new.insight, new.tags, new.pattern_bad, new.pattern_good);
302
+ END;
303
+
304
+ CREATE INDEX IF NOT EXISTS idx_lessons_created ON lessons(created);
305
+ CREATE INDEX IF NOT EXISTS idx_lessons_confirmed ON lessons(confirmed);
306
+ CREATE INDEX IF NOT EXISTS idx_lessons_severity ON lessons(severity);
307
+ CREATE INDEX IF NOT EXISTS idx_lessons_type ON lessons(type);
308
+
309
+ CREATE TABLE IF NOT EXISTS metadata (
310
+ key TEXT PRIMARY KEY,
311
+ value TEXT NOT NULL
312
+ );
313
+ `;
314
+ function createSchema(database) {
315
+ database.exec(SCHEMA_SQL);
316
+ database.pragma(`user_version = ${SCHEMA_VERSION}`);
317
+ }
318
+
319
+ // src/memory/storage/sqlite/connection.ts
320
+ var DB_PATH = ".claude/.cache/lessons.sqlite";
321
+ var dbMap = /* @__PURE__ */ new Map();
322
+ function hasExpectedVersion(database) {
323
+ const row = database.pragma("user_version", { simple: true });
324
+ return row === SCHEMA_VERSION;
325
+ }
326
+ function openDb(repoRoot, options = {}) {
327
+ const { inMemory = false } = options;
328
+ const key = inMemory ? `:memory:${repoRoot}` : join(repoRoot, DB_PATH);
329
+ const cached = dbMap.get(key);
330
+ if (cached) {
331
+ return cached;
332
+ }
333
+ const Database = getDatabaseConstructor();
334
+ let database;
335
+ if (inMemory) {
336
+ database = new Database(":memory:");
337
+ } else {
338
+ const dir = dirname(key);
339
+ mkdirSync(dir, { recursive: true });
340
+ database = new Database(key);
341
+ if (!hasExpectedVersion(database)) {
342
+ database.close();
343
+ unlinkSync(key);
344
+ database = new Database(key);
345
+ }
346
+ database.pragma("journal_mode = WAL");
347
+ }
348
+ createSchema(database);
349
+ dbMap.set(key, database);
350
+ return database;
351
+ }
352
+ function closeDb() {
353
+ for (const database of dbMap.values()) {
354
+ database.close();
355
+ }
356
+ dbMap.clear();
357
+ }
358
+ function contentHash(trigger, insight) {
359
+ return createHash("sha256").update(`${trigger} ${insight}`).digest("hex");
360
+ }
361
+ function getCachedEmbedding(repoRoot, lessonId, expectedHash) {
362
+ const database = openDb(repoRoot);
363
+ const row = database.prepare("SELECT embedding, content_hash FROM lessons WHERE id = ?").get(lessonId);
364
+ if (!row || !row.embedding || !row.content_hash) {
365
+ return null;
366
+ }
367
+ if (expectedHash && row.content_hash !== expectedHash) {
368
+ return null;
369
+ }
370
+ const float32 = new Float32Array(
371
+ row.embedding.buffer,
372
+ row.embedding.byteOffset,
373
+ row.embedding.byteLength / 4
374
+ );
375
+ return Array.from(float32);
376
+ }
377
+ function setCachedEmbedding(repoRoot, lessonId, embedding, hash) {
378
+ const database = openDb(repoRoot);
379
+ const float32 = embedding instanceof Float32Array ? embedding : new Float32Array(embedding);
380
+ const buffer = Buffer.from(float32.buffer, float32.byteOffset, float32.byteLength);
381
+ database.prepare("UPDATE lessons SET embedding = ?, content_hash = ? WHERE id = ?").run(buffer, hash, lessonId);
382
+ }
383
+
384
+ // src/memory/storage/sqlite/search.ts
385
+ function incrementRetrievalCount(repoRoot, lessonIds) {
386
+ if (lessonIds.length === 0) return;
387
+ const database = openDb(repoRoot);
388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
389
+ const update = database.prepare(`
390
+ UPDATE lessons
391
+ SET retrieval_count = retrieval_count + 1,
392
+ last_retrieved = ?
393
+ WHERE id = ?
394
+ `);
395
+ const updateMany = database.transaction((ids) => {
396
+ for (const id of ids) {
397
+ update.run(now, id);
398
+ }
399
+ });
400
+ updateMany(lessonIds);
401
+ }
402
+
403
+ // src/utils.ts
404
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
405
+ function getLessonAgeDays(lesson) {
406
+ const created = new Date(lesson.created).getTime();
407
+ const now = Date.now();
408
+ return Math.floor((now - created) / MS_PER_DAY);
409
+ }
410
+
411
+ // src/memory/retrieval/session.ts
412
+ var DEFAULT_LIMIT = 5;
413
+ function hasSeverity(item) {
414
+ return item.severity !== void 0;
415
+ }
416
+ async function loadSessionLessons(repoRoot, limit = DEFAULT_LIMIT) {
417
+ const { items } = await readMemoryItems(repoRoot);
418
+ const highSeverityLessons = items.filter(
419
+ (item) => hasSeverity(item) && item.severity === "high" && item.confirmed && !item.invalidatedAt
420
+ );
421
+ highSeverityLessons.sort((a, b) => {
422
+ const dateA = new Date(a.created).getTime();
423
+ const dateB = new Date(b.created).getTime();
424
+ return dateB - dateA;
425
+ });
426
+ const topLessons = highSeverityLessons.slice(0, limit);
427
+ if (topLessons.length > 0) {
428
+ incrementRetrievalCount(repoRoot, topLessons.map((lesson) => lesson.id));
429
+ }
430
+ return topLessons;
431
+ }
432
+ var CCT_PATTERNS_PATH = ".claude/lessons/cct-patterns.jsonl";
433
+ var CctPatternSchema = z.object({
434
+ id: z.string().regex(/^CCT-[a-f0-9]{8}$/),
435
+ name: z.string().min(1),
436
+ description: z.string().min(1),
437
+ frequency: z.number().int().positive(),
438
+ testable: z.boolean(),
439
+ testApproach: z.string().optional(),
440
+ sourceIds: z.array(z.string()).min(1),
441
+ created: z.string()
442
+ // ISO8601
443
+ });
444
+
445
+ // src/compound/io.ts
446
+ async function readCctPatterns(repoRoot) {
447
+ const filePath = join(repoRoot, CCT_PATTERNS_PATH);
448
+ let content;
449
+ try {
450
+ content = await readFile(filePath, "utf-8");
451
+ } catch (err) {
452
+ if (err.code === "ENOENT") {
453
+ return [];
454
+ }
455
+ throw err;
456
+ }
457
+ const patterns = [];
458
+ const lines = content.split("\n");
459
+ for (const line of lines) {
460
+ const trimmed = line.trim();
461
+ if (!trimmed) continue;
462
+ const parsed = JSON.parse(trimmed);
463
+ const result = CctPatternSchema.safeParse(parsed);
464
+ if (result.success) {
465
+ patterns.push(result.data);
466
+ }
467
+ }
468
+ return patterns;
469
+ }
470
+ var MODEL_URI = "hf:ggml-org/embeddinggemma-300M-qat-q4_0-GGUF/embeddinggemma-300M-qat-Q4_0.gguf";
471
+ join(homedir(), ".node-llama-cpp", "models");
472
+ async function resolveModel(options = {}) {
473
+ const { cli = true } = options;
474
+ return resolveModelFile(MODEL_URI, { cli });
475
+ }
476
+
477
+ // src/memory/embeddings/nomic.ts
478
+ var embeddingContext = null;
479
+ var pendingInit = null;
480
+ var llamaInstance = null;
481
+ var modelInstance = null;
482
+ async function getEmbedding() {
483
+ if (embeddingContext) return embeddingContext;
484
+ if (pendingInit) return pendingInit;
485
+ pendingInit = (async () => {
486
+ try {
487
+ const modelPath = await resolveModel({ cli: true });
488
+ llamaInstance = await getLlama();
489
+ modelInstance = await llamaInstance.loadModel({ modelPath });
490
+ embeddingContext = await modelInstance.createEmbeddingContext();
491
+ return embeddingContext;
492
+ } catch (err) {
493
+ pendingInit = null;
494
+ throw err;
495
+ }
496
+ })();
497
+ return pendingInit;
498
+ }
499
+ async function embedText(text) {
500
+ const ctx = await getEmbedding();
501
+ const result = await ctx.getEmbeddingFor(text);
502
+ return Array.from(result.vector);
503
+ }
504
+
505
+ // src/memory/search/vector.ts
506
+ function cosineSimilarity(a, b) {
507
+ if (a.length !== b.length) {
508
+ throw new Error("Vectors must have same length");
509
+ }
510
+ let dotProduct = 0;
511
+ let normA = 0;
512
+ let normB = 0;
513
+ for (let i = 0; i < a.length; i++) {
514
+ dotProduct += a[i] * b[i];
515
+ normA += a[i] * a[i];
516
+ normB += b[i] * b[i];
517
+ }
518
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
519
+ if (magnitude === 0) return 0;
520
+ return dotProduct / magnitude;
521
+ }
522
+ var DEFAULT_LIMIT2 = 10;
523
+ function cctToMemoryItem(pattern) {
524
+ return {
525
+ id: pattern.id,
526
+ type: "lesson",
527
+ trigger: pattern.name,
528
+ insight: pattern.description,
529
+ tags: [],
530
+ source: "manual",
531
+ context: { tool: "compound", intent: "synthesis" },
532
+ created: pattern.created,
533
+ confirmed: true,
534
+ supersedes: [],
535
+ related: pattern.sourceIds
536
+ };
537
+ }
538
+ async function searchVector(repoRoot, query, options) {
539
+ const limit = options?.limit ?? DEFAULT_LIMIT2;
540
+ const { items } = await readMemoryItems(repoRoot);
541
+ let cctPatterns = [];
542
+ try {
543
+ cctPatterns = await readCctPatterns(repoRoot);
544
+ } catch {
545
+ }
546
+ if (items.length === 0 && cctPatterns.length === 0) return [];
547
+ const queryVector = await embedText(query);
548
+ const scored = [];
549
+ for (const item of items) {
550
+ if (item.invalidatedAt) continue;
551
+ try {
552
+ const itemText = `${item.trigger} ${item.insight}`;
553
+ const hash = contentHash(item.trigger, item.insight);
554
+ let itemVector = getCachedEmbedding(repoRoot, item.id, hash);
555
+ if (!itemVector) {
556
+ itemVector = await embedText(itemText);
557
+ setCachedEmbedding(repoRoot, item.id, itemVector, hash);
558
+ }
559
+ const score = cosineSimilarity(queryVector, itemVector);
560
+ scored.push({ lesson: item, score });
561
+ } catch {
562
+ continue;
563
+ }
564
+ }
565
+ for (const pattern of cctPatterns) {
566
+ try {
567
+ const text = `${pattern.name} ${pattern.description}`;
568
+ const vec = await embedText(text);
569
+ const score = cosineSimilarity(queryVector, vec);
570
+ scored.push({ lesson: cctToMemoryItem(pattern), score });
571
+ } catch {
572
+ continue;
573
+ }
574
+ }
575
+ scored.sort((a, b) => b.score - a.score);
576
+ return scored.slice(0, limit);
577
+ }
578
+
579
+ // src/memory/search/ranking.ts
580
+ var RECENCY_THRESHOLD_DAYS = 30;
581
+ var HIGH_SEVERITY_BOOST = 1.5;
582
+ var MEDIUM_SEVERITY_BOOST = 1;
583
+ var LOW_SEVERITY_BOOST = 0.8;
584
+ var RECENCY_BOOST = 1.2;
585
+ var CONFIRMATION_BOOST = 1.3;
586
+ var MAX_COMBINED_BOOST = 1.8;
587
+ function severityBoost(item) {
588
+ switch (item.severity) {
589
+ case "high":
590
+ return HIGH_SEVERITY_BOOST;
591
+ case "medium":
592
+ return MEDIUM_SEVERITY_BOOST;
593
+ case "low":
594
+ return LOW_SEVERITY_BOOST;
595
+ default:
596
+ return MEDIUM_SEVERITY_BOOST;
597
+ }
598
+ }
599
+ function recencyBoost(item) {
600
+ const ageDays = getLessonAgeDays(item);
601
+ return ageDays <= RECENCY_THRESHOLD_DAYS ? RECENCY_BOOST : 1;
602
+ }
603
+ function confirmationBoost(item) {
604
+ return item.confirmed ? CONFIRMATION_BOOST : 1;
605
+ }
606
+ function calculateScore(item, vectorSimilarity) {
607
+ const boost = Math.min(
608
+ severityBoost(item) * recencyBoost(item) * confirmationBoost(item),
609
+ MAX_COMBINED_BOOST
610
+ );
611
+ return vectorSimilarity * boost;
612
+ }
613
+ function rankLessons(lessons) {
614
+ return lessons.map((scored) => ({
615
+ ...scored,
616
+ finalScore: calculateScore(scored.lesson, scored.score)
617
+ })).sort((a, b) => (b.finalScore ?? 0) - (a.finalScore ?? 0));
618
+ }
619
+
620
+ // src/commands/management-prime.ts
621
+ var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
622
+
623
+ > **Context Recovery**: Run \`ca prime\` after compaction, clear, or new session
624
+
625
+ ## MCP Tools (ALWAYS USE THESE)
626
+
627
+ **You MUST use MCP tools, NOT CLI commands:**
628
+
629
+ | Tool | Purpose |
630
+ |------|---------|
631
+ | \`memory_search\` | Search lessons - call BEFORE architectural decisions |
632
+ | \`memory_capture\` | Capture lessons - call AFTER corrections or discoveries |
633
+
634
+ ## Core Constraints
635
+
636
+ **Default**: Use MCP tools for lesson management
637
+ **Prohibited**: NEVER edit .claude/lessons/ files directly
638
+
639
+ **Default**: Propose lessons freely after corrections
640
+ **Prohibited**: NEVER propose without quality gate (novel + specific; prefer actionable)
641
+
642
+ ## Retrieval Protocol
643
+
644
+ You MUST call \`memory_search\` BEFORE:
645
+ - Architectural decisions or complex planning
646
+ - Implementing patterns you've done before in this repo
647
+
648
+ **NEVER skip memory_search for complex decisions.** Past mistakes will repeat.
649
+
650
+ ## Capture Protocol
651
+
652
+ Call \`memory_capture\` AFTER:
653
+ - User corrects you ("no", "wrong", "actually...")
654
+ - You self-correct after iteration failures
655
+ - Test fails then you fix it
656
+
657
+ **Quality gate** (must pass before capturing):
658
+ - Novel (not already stored)
659
+ - Specific (clear guidance)
660
+ - Actionable (preferred, not mandatory)
661
+
662
+ **Workflow**: Search BEFORE deciding, capture AFTER learning.
663
+
664
+ ## CLI (fallback only)
665
+
666
+ When MCP is unavailable: \`ca search "query"\`, \`ca learn "insight"\`, \`ca list\`
667
+ `;
668
+ function formatSource(source) {
669
+ switch (source) {
670
+ case "user_correction":
671
+ return "user correction";
672
+ case "self_correction":
673
+ return "self correction";
674
+ case "test_failure":
675
+ return "test failure";
676
+ case "manual":
677
+ return "manual";
678
+ default:
679
+ return source;
680
+ }
681
+ }
682
+ function formatLessonForPrime(lesson) {
683
+ const date = lesson.created.slice(0, 10);
684
+ const tags = lesson.tags.length > 0 ? ` (${lesson.tags.join(", ")})` : "";
685
+ const source = formatSource(lesson.source);
686
+ return `- **${lesson.insight}**${tags}
687
+ Learned: ${date} via ${source}`;
688
+ }
689
+ async function getPrimeContext(repoRoot) {
690
+ const root = repoRoot ?? getRepoRoot();
691
+ const lessons = await loadSessionLessons(root, 5);
692
+ let output = TRUST_LANGUAGE_TEMPLATE;
693
+ if (lessons.length > 0) {
694
+ const formattedLessons = lessons.map(formatLessonForPrime).join("\n\n");
695
+ output += `
696
+ ---
697
+
698
+ # [CRITICAL] Mandatory Recall
699
+
700
+ Critical lessons from past corrections:
701
+
702
+ ${formattedLessons}
703
+ `;
704
+ }
705
+ return output;
706
+ }
707
+
708
+ // src/memory/capture/triggers.ts
709
+ var PATTERN_INDICATORS = [
710
+ /\buse\s+.+\s+instead\s+of\b/i,
711
+ /\bprefer\s+.+\s+(over|to)\b/i
712
+ ];
713
+ var SOLUTION_INDICATORS = [
714
+ /\bwhen\s+.+,\s/i,
715
+ /\bif\s+.+\bthen\b/i,
716
+ /\bif\s+.+,\s/i,
717
+ /\bto\s+fix\b/i
718
+ ];
719
+ var PREFERENCE_INDICATORS = [
720
+ /\balways\s+/i,
721
+ /\bnever\s+/i
722
+ ];
723
+ function inferMemoryItemType(insight) {
724
+ for (const pattern of PATTERN_INDICATORS) {
725
+ if (pattern.test(insight)) return "pattern";
726
+ }
727
+ for (const pattern of SOLUTION_INDICATORS) {
728
+ if (pattern.test(insight)) return "solution";
729
+ }
730
+ for (const pattern of PREFERENCE_INDICATORS) {
731
+ if (pattern.test(insight)) return "preference";
732
+ }
733
+ return "lesson";
734
+ }
735
+ var CorrectionSignalSchema = z.object({
736
+ messages: z.array(z.string()),
737
+ context: ContextSchema
738
+ });
739
+ var EditEntrySchema = z.object({
740
+ file: z.string(),
741
+ success: z.boolean(),
742
+ timestamp: z.number()
743
+ });
744
+ var EditHistorySchema = z.object({
745
+ edits: z.array(EditEntrySchema)
746
+ });
747
+ var TestResultSchema = z.object({
748
+ passed: z.boolean(),
749
+ output: z.string(),
750
+ testFile: z.string()
751
+ });
752
+ z.discriminatedUnion("type", [
753
+ z.object({ type: z.literal("user"), data: CorrectionSignalSchema }),
754
+ z.object({ type: z.literal("self"), data: EditHistorySchema }),
755
+ z.object({ type: z.literal("test"), data: TestResultSchema })
756
+ ]);
757
+ var SeveritySchema2 = z.enum(["error", "warning", "info"]);
758
+ var FilePatternCheckSchema = z.object({
759
+ type: z.literal("file-pattern"),
760
+ glob: z.string(),
761
+ pattern: z.string(),
762
+ mustMatch: z.boolean().optional()
763
+ });
764
+ var FileSizeCheckSchema = z.object({
765
+ type: z.literal("file-size"),
766
+ glob: z.string(),
767
+ maxLines: z.number().int().positive()
768
+ });
769
+ var ScriptCheckSchema = z.object({
770
+ type: z.literal("script"),
771
+ command: z.string(),
772
+ expectExitCode: z.number().int().optional()
773
+ });
774
+ var RuleCheckSchema = z.discriminatedUnion("type", [
775
+ FilePatternCheckSchema,
776
+ FileSizeCheckSchema,
777
+ ScriptCheckSchema
778
+ ]);
779
+ var RuleSchema = z.object({
780
+ id: z.string().min(1),
781
+ description: z.string(),
782
+ severity: SeveritySchema2,
783
+ check: RuleCheckSchema,
784
+ remediation: z.string()
785
+ });
786
+ z.object({
787
+ rules: z.array(RuleSchema)
788
+ });
789
+ var AuditFindingSchema = z.object({
790
+ file: z.string(),
791
+ issue: z.string(),
792
+ severity: z.enum(["error", "warning", "info"]),
793
+ relatedLessonId: z.string().optional(),
794
+ suggestedFix: z.string().optional(),
795
+ source: z.enum(["rule", "pattern", "lesson"])
796
+ });
797
+ var AuditSummarySchema = z.object({
798
+ errors: z.number(),
799
+ warnings: z.number(),
800
+ infos: z.number(),
801
+ filesChecked: z.number()
802
+ });
803
+ z.object({
804
+ findings: z.array(AuditFindingSchema),
805
+ summary: AuditSummarySchema,
806
+ timestamp: z.string()
807
+ });
808
+
809
+ // src/index.ts
810
+ var _require = createRequire(import.meta.url);
811
+ var _pkg = _require("../package.json");
812
+ var VERSION = _pkg.version;
813
+
814
+ // src/mcp.ts
815
+ var DEFAULT_MAX_RESULTS = 5;
816
+ var MIN_INSIGHT_LENGTH = 10;
817
+ function isSearchError(result) {
818
+ return "error" in result && result.error !== void 0;
819
+ }
820
+ async function handleSearch(repoRoot, query, maxResults, typeFilter) {
821
+ try {
822
+ const limit = maxResults ?? DEFAULT_MAX_RESULTS;
823
+ const searchLimit = typeFilter ? limit * 3 : limit;
824
+ const results = await searchVector(repoRoot, query, { limit: searchLimit });
825
+ const ranked = rankLessons(results);
826
+ let lessons = ranked.map((r) => ({
827
+ lesson: r.lesson,
828
+ score: r.score,
829
+ finalScore: r.finalScore
830
+ }));
831
+ if (typeFilter) {
832
+ lessons = lessons.filter((r) => r.lesson.type === typeFilter);
833
+ }
834
+ lessons = lessons.slice(0, limit);
835
+ const ids = lessons.map((r) => r.lesson.id);
836
+ if (ids.length > 0) {
837
+ incrementRetrievalCount(repoRoot, ids);
838
+ }
839
+ return { lessons };
840
+ } catch (err) {
841
+ const message = err instanceof Error ? err.message : "Unknown error";
842
+ return {
843
+ error: `Search failed: ${message}`,
844
+ action: "Run: npx ca download-model",
845
+ lessons: []
846
+ };
847
+ }
848
+ }
849
+ async function handleCapture(repoRoot, insight, trigger, tags, type, pattern, severity, confirmed, supersedes, related) {
850
+ let itemType = type ?? inferMemoryItemType(insight);
851
+ if (type === "pattern" && !pattern) {
852
+ throw new Error("Pattern type requires a pattern field with { bad, good }");
853
+ }
854
+ if (itemType === "pattern" && !pattern && !type) {
855
+ itemType = "lesson";
856
+ }
857
+ const item = {
858
+ type: itemType,
859
+ id: generateId(insight, itemType),
860
+ trigger: trigger ?? "Manual capture via MCP",
861
+ insight,
862
+ tags: tags ?? [],
863
+ source: "manual",
864
+ context: { tool: "mcp", intent: "memory capture" },
865
+ created: (/* @__PURE__ */ new Date()).toISOString(),
866
+ ...severity ? { severity } : {},
867
+ confirmed: confirmed ?? true,
868
+ supersedes: supersedes ?? [],
869
+ related: related ?? [],
870
+ ...pattern ? { pattern } : {}
871
+ };
872
+ await appendMemoryItem(repoRoot, item);
873
+ return { item, lesson: item };
874
+ }
875
+ var searchInputSchema = {
876
+ query: z.string().min(1, "Query must be non-empty"),
877
+ maxResults: z.number().int().positive().max(100).optional(),
878
+ type: MemoryItemTypeSchema.optional()
879
+ };
880
+ var captureInputSchema = {
881
+ insight: z.string().min(MIN_INSIGHT_LENGTH, `Insight must be at least ${MIN_INSIGHT_LENGTH} characters`),
882
+ trigger: z.string().min(1).optional(),
883
+ tags: z.array(z.string().min(1)).optional(),
884
+ type: MemoryItemTypeSchema.optional(),
885
+ pattern: PatternSchema.optional(),
886
+ severity: z.enum(["high", "medium", "low"]).optional(),
887
+ confirmed: z.boolean().optional(),
888
+ supersedes: z.array(z.string()).optional(),
889
+ related: z.array(z.string()).optional()
890
+ };
891
+ function registerSearchTool(server, repoRoot, toolHandlers) {
892
+ server.registerTool(
893
+ "memory_search",
894
+ {
895
+ title: "Search Memory",
896
+ description: `Mandatory recall: search memory BEFORE:
897
+ - Architectural decisions or complex planning
898
+ - Patterns you've implemented before in this repo
899
+ - After corrections ("actually...", "wrong", "use X instead")
900
+
901
+ Returns relevant memory items ranked by similarity and severity.`,
902
+ inputSchema: searchInputSchema
903
+ },
904
+ async ({ query, maxResults, type: typeFilter }) => {
905
+ const output = await handleSearch(repoRoot, query, maxResults, typeFilter);
906
+ return {
907
+ content: [{ type: "text", text: JSON.stringify(output) }]
908
+ };
909
+ }
910
+ );
911
+ toolHandlers["memory_search"] = async (params) => {
912
+ const parsed = z.object(searchInputSchema).parse(params);
913
+ return handleSearch(repoRoot, parsed.query, parsed.maxResults, parsed.type);
914
+ };
915
+ }
916
+ function registerCaptureTool(server, repoRoot, toolHandlers) {
917
+ server.registerTool(
918
+ "memory_capture",
919
+ {
920
+ title: "Capture Memory",
921
+ description: `Capture a memory item AFTER:
922
+ - User corrects you ("no", "actually...", "use X instead")
923
+ - Test fail -> fix -> pass cycles
924
+ - Discovering project-specific knowledge
925
+
926
+ Types: lesson (default), solution, pattern (requires pattern field), preference.
927
+ Saves immediately and shows what was captured.`,
928
+ inputSchema: captureInputSchema
929
+ },
930
+ async ({ insight, trigger, tags, type, pattern, severity, confirmed, supersedes, related }) => {
931
+ const output = await handleCapture(repoRoot, insight, trigger, tags, type, pattern, severity, confirmed, supersedes, related);
932
+ return {
933
+ content: [{ type: "text", text: JSON.stringify(output) }]
934
+ };
935
+ }
936
+ );
937
+ toolHandlers["memory_capture"] = async (params) => {
938
+ const parsed = z.object(captureInputSchema).parse(params);
939
+ return handleCapture(repoRoot, parsed.insight, parsed.trigger, parsed.tags, parsed.type, parsed.pattern, parsed.severity, parsed.confirmed, parsed.supersedes, parsed.related);
940
+ };
941
+ }
942
+ function registerPrimeResource(server, repoRoot, resourceHandlers) {
943
+ server.registerResource(
944
+ "prime",
945
+ "memory://prime",
946
+ {
947
+ title: "Prime Context",
948
+ description: "Workflow context with high-severity memory items for session start",
949
+ mimeType: "text/plain"
950
+ },
951
+ async (uri) => {
952
+ const content = await getPrimeContext(repoRoot);
953
+ return {
954
+ contents: [{ uri: uri.href, text: content }]
955
+ };
956
+ }
957
+ );
958
+ resourceHandlers["memory://prime"] = async () => {
959
+ const content = await getPrimeContext(repoRoot);
960
+ return { content };
961
+ };
962
+ }
963
+ function createMcpServer(repoRoot) {
964
+ const server = new McpServer({
965
+ name: "compound-agent",
966
+ version: VERSION
967
+ });
968
+ const toolHandlers = {};
969
+ const resourceHandlers = {};
970
+ registerSearchTool(server, repoRoot, toolHandlers);
971
+ registerCaptureTool(server, repoRoot, toolHandlers);
972
+ registerPrimeResource(server, repoRoot, resourceHandlers);
973
+ return {
974
+ server,
975
+ repoRoot,
976
+ async callTool(name, params) {
977
+ const handler = toolHandlers[name];
978
+ if (!handler) {
979
+ throw new Error(`Unknown tool: ${name}`);
980
+ }
981
+ return handler(params);
982
+ },
983
+ async readResource(uri) {
984
+ const handler = resourceHandlers[uri];
985
+ if (!handler) {
986
+ throw new Error(`Unknown resource: ${uri}`);
987
+ }
988
+ return handler();
989
+ }
990
+ };
991
+ }
992
+ function registerMcpCleanup() {
993
+ const cleanup = () => {
994
+ try {
995
+ closeDb();
996
+ } catch {
997
+ }
998
+ };
999
+ process.on("SIGINT", () => {
1000
+ cleanup();
1001
+ process.exit(0);
1002
+ });
1003
+ process.on("SIGTERM", () => {
1004
+ cleanup();
1005
+ process.exit(0);
1006
+ });
1007
+ }
1008
+ async function main() {
1009
+ registerMcpCleanup();
1010
+ const repoRoot = process.cwd();
1011
+ const { server } = createMcpServer(repoRoot);
1012
+ const transport = new StdioServerTransport();
1013
+ await server.connect(transport);
1014
+ }
1015
+ var isMainModule = import.meta.url === `file://${process.argv[1]}`;
1016
+ if (isMainModule) {
1017
+ main().catch((err) => {
1018
+ console.error("MCP Server error:", err);
1019
+ process.exit(1);
1020
+ });
1021
+ }
1022
+
1023
+ export { createMcpServer, isSearchError, registerMcpCleanup };
1024
+ //# sourceMappingURL=mcp.js.map
1025
+ //# sourceMappingURL=mcp.js.map