@colbymchenry/cmem 0.2.37 → 0.5.2

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/cli.js CHANGED
@@ -1,194 +1,9 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
-
12
- // src/commands/install.ts
13
- var install_exports = {};
14
- __export(install_exports, {
15
- installCommand: () => installCommand,
16
- statusCommand: () => statusCommand,
17
- uninstallCommand: () => uninstallCommand
18
- });
19
- import chalk9 from "chalk";
20
- import { writeFileSync, unlinkSync as unlinkSync2, existsSync as existsSync10, mkdirSync as mkdirSync2 } from "fs";
21
- import { homedir as homedir2 } from "os";
22
- import { join as join6, dirname as dirname6 } from "path";
23
- import { execSync as execSync2 } from "child_process";
24
- function getCmemPath() {
25
- try {
26
- const result = execSync2("which cmem", { encoding: "utf-8" }).trim();
27
- return result;
28
- } catch {
29
- return "/usr/local/bin/cmem";
30
- }
31
- }
32
- function getNodeBinDir() {
33
- try {
34
- const nodePath = execSync2("which node", { encoding: "utf-8" }).trim();
35
- return dirname6(nodePath);
36
- } catch {
37
- return "/usr/local/bin";
38
- }
39
- }
40
- function generatePlist(cmemPath) {
41
- const nodeBinDir = getNodeBinDir();
42
- const pathValue = `${nodeBinDir}:/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin`;
43
- return `<?xml version="1.0" encoding="UTF-8"?>
44
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
45
- <plist version="1.0">
46
- <dict>
47
- <key>Label</key>
48
- <string>com.cmem.watch</string>
49
-
50
- <key>ProgramArguments</key>
51
- <array>
52
- <string>${cmemPath}</string>
53
- <string>watch</string>
54
- </array>
55
-
56
- <key>RunAtLoad</key>
57
- <true/>
58
-
59
- <key>KeepAlive</key>
60
- <true/>
61
-
62
- <key>StandardOutPath</key>
63
- <string>${homedir2()}/.cmem/watch.log</string>
64
-
65
- <key>StandardErrorPath</key>
66
- <string>${homedir2()}/.cmem/watch.error.log</string>
67
-
68
- <key>EnvironmentVariables</key>
69
- <dict>
70
- <key>PATH</key>
71
- <string>${pathValue}</string>
72
- </dict>
73
- </dict>
74
- </plist>`;
75
- }
76
- async function installCommand() {
77
- const platform = process.platform;
78
- if (platform !== "darwin") {
79
- console.log(chalk9.yellow("Auto-install is currently only supported on macOS."));
80
- console.log(chalk9.dim("\nFor Linux, create a systemd service:"));
81
- console.log(chalk9.dim(" ~/.config/systemd/user/cmem-watch.service"));
82
- console.log(chalk9.dim("\nFor manual background run:"));
83
- console.log(chalk9.dim(" nohup cmem watch > ~/.cmem/watch.log 2>&1 &"));
84
- return;
85
- }
86
- console.log(chalk9.cyan("Installing cmem watch daemon...\n"));
87
- if (!existsSync10(LAUNCH_AGENTS_DIR)) {
88
- mkdirSync2(LAUNCH_AGENTS_DIR, { recursive: true });
89
- }
90
- const cmemDir = join6(homedir2(), ".cmem");
91
- if (!existsSync10(cmemDir)) {
92
- mkdirSync2(cmemDir, { recursive: true });
93
- }
94
- const cmemPath = getCmemPath();
95
- console.log(chalk9.dim(`Using cmem at: ${cmemPath}`));
96
- if (existsSync10(PLIST_PATH)) {
97
- console.log(chalk9.yellow("LaunchAgent already exists. Unloading first..."));
98
- try {
99
- execSync2(`launchctl unload "${PLIST_PATH}"`, { stdio: "ignore" });
100
- } catch {
101
- }
102
- }
103
- const plistContent = generatePlist(cmemPath);
104
- writeFileSync(PLIST_PATH, plistContent);
105
- console.log(chalk9.green(`\u2713 Created ${PLIST_PATH}`));
106
- try {
107
- execSync2(`launchctl load "${PLIST_PATH}"`);
108
- console.log(chalk9.green("\u2713 LaunchAgent loaded"));
109
- } catch (err) {
110
- console.log(chalk9.red("Failed to load LaunchAgent:"), err);
111
- return;
112
- }
113
- console.log(chalk9.green("\n\u2713 cmem watch daemon installed and running!"));
114
- console.log(chalk9.dim("\nThe daemon will:"));
115
- console.log(chalk9.dim(" \u2022 Start automatically on login"));
116
- console.log(chalk9.dim(" \u2022 Restart if it crashes"));
117
- console.log(chalk9.dim(` \u2022 Log to ~/.cmem/watch.log`));
118
- console.log(chalk9.dim("\nCommands:"));
119
- console.log(chalk9.dim(" cmem uninstall Stop and remove the daemon"));
120
- console.log(chalk9.dim(" cmem status Check daemon status"));
121
- }
122
- async function uninstallCommand() {
123
- const platform = process.platform;
124
- if (platform !== "darwin") {
125
- console.log(chalk9.yellow("Auto-uninstall is currently only supported on macOS."));
126
- return;
127
- }
128
- console.log(chalk9.cyan("Uninstalling cmem watch daemon...\n"));
129
- if (!existsSync10(PLIST_PATH)) {
130
- console.log(chalk9.yellow("LaunchAgent not found. Nothing to uninstall."));
131
- return;
132
- }
133
- try {
134
- execSync2(`launchctl unload "${PLIST_PATH}"`);
135
- console.log(chalk9.green("\u2713 LaunchAgent unloaded"));
136
- } catch {
137
- console.log(chalk9.yellow("LaunchAgent was not loaded"));
138
- }
139
- try {
140
- unlinkSync2(PLIST_PATH);
141
- console.log(chalk9.green(`\u2713 Removed ${PLIST_PATH}`));
142
- } catch (err) {
143
- console.log(chalk9.red("Failed to remove plist:"), err);
144
- return;
145
- }
146
- console.log(chalk9.green("\n\u2713 cmem watch daemon uninstalled"));
147
- }
148
- async function statusCommand() {
149
- const platform = process.platform;
150
- if (platform !== "darwin") {
151
- console.log(chalk9.yellow("Status check is currently only supported on macOS."));
152
- return;
153
- }
154
- console.log(chalk9.cyan("cmem watch daemon status\n"));
155
- if (!existsSync10(PLIST_PATH)) {
156
- console.log(chalk9.yellow("Status: Not installed"));
157
- console.log(chalk9.dim("Run: cmem install"));
158
- return;
159
- }
160
- try {
161
- const result = execSync2(`launchctl list | grep com.cmem.watch`, {
162
- encoding: "utf-8"
163
- });
164
- const parts = result.trim().split(/\s+/);
165
- const pid = parts[0];
166
- const exitCode = parts[1];
167
- if (pid && pid !== "-") {
168
- console.log(chalk9.green(`Status: Running (PID ${pid})`));
169
- } else if (exitCode === "0") {
170
- console.log(chalk9.yellow("Status: Stopped (exit code 0)"));
171
- } else {
172
- console.log(chalk9.red(`Status: Crashed (exit code ${exitCode})`));
173
- console.log(chalk9.dim("Check ~/.cmem/watch.error.log for details"));
174
- }
175
- } catch {
176
- console.log(chalk9.yellow("Status: Not running"));
177
- console.log(chalk9.dim("The daemon is installed but not currently running."));
178
- console.log(chalk9.dim("It will start on next login, or run: launchctl load ~/Library/LaunchAgents/com.cmem.watch.plist"));
179
- }
180
- console.log(chalk9.dim(`
181
- Plist: ${PLIST_PATH}`));
182
- console.log(chalk9.dim("Logs: ~/.cmem/watch.log"));
183
- }
184
- var LAUNCH_AGENTS_DIR, PLIST_NAME, PLIST_PATH;
185
- var init_install = __esm({
186
- "src/commands/install.ts"() {
187
- "use strict";
188
- LAUNCH_AGENTS_DIR = join6(homedir2(), "Library", "LaunchAgents");
189
- PLIST_NAME = "com.cmem.watch.plist";
190
- PLIST_PATH = join6(LAUNCH_AGENTS_DIR, PLIST_NAME);
191
- }
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
192
7
  });
193
8
 
194
9
  // src/cli.ts
@@ -197,7 +12,7 @@ import { render } from "ink";
197
12
  import React2 from "react";
198
13
  import { existsSync as existsSync12, readFileSync as readFileSync4 } from "fs";
199
14
  import { join as join8, dirname as dirname8 } from "path";
200
- import { fileURLToPath as fileURLToPath2 } from "url";
15
+ import { fileURLToPath as fileURLToPath3 } from "url";
201
16
  import { spawn as spawn2 } from "child_process";
202
17
 
203
18
  // src/ui/App.tsx
@@ -583,6 +398,88 @@ function initSchema(database) {
583
398
  database.exec(`
584
399
  CREATE INDEX IF NOT EXISTS idx_favorites_type ON favorites(type);
585
400
  `);
401
+ database.exec(`
402
+ CREATE TABLE IF NOT EXISTS lessons (
403
+ id TEXT PRIMARY KEY,
404
+ project_path TEXT NOT NULL,
405
+ category TEXT NOT NULL CHECK (category IN (
406
+ 'architecture_decision', 'anti_pattern', 'bug_pattern',
407
+ 'project_convention', 'dependency_knowledge', 'domain_knowledge',
408
+ 'workflow', 'other'
409
+ )),
410
+ title TEXT NOT NULL,
411
+ trigger_context TEXT NOT NULL,
412
+ insight TEXT NOT NULL,
413
+ reasoning TEXT,
414
+ confidence REAL DEFAULT 0.5,
415
+ times_applied INTEGER DEFAULT 0,
416
+ times_validated INTEGER DEFAULT 0,
417
+ times_rejected INTEGER DEFAULT 0,
418
+ source_session_id TEXT,
419
+ source_type TEXT DEFAULT 'synthesized',
420
+ archived INTEGER DEFAULT 0,
421
+ created_at TEXT DEFAULT (datetime('now')),
422
+ updated_at TEXT DEFAULT (datetime('now')),
423
+ last_applied_at TEXT
424
+ );
425
+ `);
426
+ database.exec(`
427
+ CREATE VIRTUAL TABLE IF NOT EXISTS lesson_embeddings USING vec0(
428
+ lesson_id TEXT PRIMARY KEY,
429
+ embedding FLOAT[${EMBEDDING_DIMENSIONS}]
430
+ );
431
+ `);
432
+ database.exec(`
433
+ CREATE TABLE IF NOT EXISTS lesson_feedback (
434
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
435
+ lesson_id TEXT NOT NULL,
436
+ session_id TEXT,
437
+ feedback_type TEXT NOT NULL CHECK (feedback_type IN (
438
+ 'validated', 'rejected', 'modified'
439
+ )),
440
+ comment TEXT,
441
+ created_at TEXT DEFAULT (datetime('now')),
442
+ FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE
443
+ );
444
+ `);
445
+ database.exec(`
446
+ CREATE TABLE IF NOT EXISTS synthesis_queue (
447
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
448
+ session_id TEXT NOT NULL UNIQUE,
449
+ project_path TEXT NOT NULL,
450
+ queued_at TEXT DEFAULT (datetime('now')),
451
+ status TEXT DEFAULT 'pending',
452
+ processed_at TEXT,
453
+ lessons_created INTEGER DEFAULT 0,
454
+ error TEXT
455
+ );
456
+ `);
457
+ database.exec(`
458
+ CREATE TABLE IF NOT EXISTS session_injections (
459
+ session_id TEXT NOT NULL,
460
+ lesson_id TEXT NOT NULL,
461
+ injected_at TEXT DEFAULT (datetime('now')),
462
+ PRIMARY KEY (session_id, lesson_id)
463
+ );
464
+ `);
465
+ database.exec(`
466
+ CREATE INDEX IF NOT EXISTS idx_lessons_project ON lessons(project_path);
467
+ `);
468
+ database.exec(`
469
+ CREATE INDEX IF NOT EXISTS idx_lessons_category ON lessons(category);
470
+ `);
471
+ database.exec(`
472
+ CREATE INDEX IF NOT EXISTS idx_lessons_confidence ON lessons(confidence DESC);
473
+ `);
474
+ database.exec(`
475
+ CREATE INDEX IF NOT EXISTS idx_lessons_archived ON lessons(archived);
476
+ `);
477
+ database.exec(`
478
+ CREATE INDEX IF NOT EXISTS idx_synthesis_queue_status ON synthesis_queue(status);
479
+ `);
480
+ database.exec(`
481
+ CREATE INDEX IF NOT EXISTS idx_session_injections_session ON session_injections(session_id);
482
+ `);
586
483
  runMigrations(database);
587
484
  }
588
485
  function runMigrations(database) {
@@ -3214,6 +3111,558 @@ function isClaudeCliAvailable() {
3214
3111
  return getClaudePath() !== null;
3215
3112
  }
3216
3113
 
3114
+ // src/db/lessons.ts
3115
+ import { randomUUID as randomUUID2 } from "crypto";
3116
+ function mapLessonRow(row) {
3117
+ return {
3118
+ id: row.id,
3119
+ projectPath: row.project_path,
3120
+ category: row.category,
3121
+ title: row.title,
3122
+ triggerContext: row.trigger_context,
3123
+ insight: row.insight,
3124
+ reasoning: row.reasoning ?? void 0,
3125
+ confidence: row.confidence,
3126
+ timesApplied: row.times_applied,
3127
+ timesValidated: row.times_validated,
3128
+ timesRejected: row.times_rejected,
3129
+ sourceSessionId: row.source_session_id ?? void 0,
3130
+ sourceType: row.source_type,
3131
+ archived: row.archived === 1,
3132
+ createdAt: row.created_at,
3133
+ updatedAt: row.updated_at,
3134
+ lastAppliedAt: row.last_applied_at ?? void 0
3135
+ };
3136
+ }
3137
+ function createLesson(input) {
3138
+ const db2 = getDatabase();
3139
+ const id = randomUUID2();
3140
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3141
+ db2.prepare(`
3142
+ INSERT INTO lessons (
3143
+ id, project_path, category, title, trigger_context, insight,
3144
+ reasoning, confidence, source_session_id, source_type,
3145
+ created_at, updated_at
3146
+ )
3147
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3148
+ `).run(
3149
+ id,
3150
+ input.projectPath,
3151
+ input.category,
3152
+ input.title,
3153
+ input.triggerContext,
3154
+ input.insight,
3155
+ input.reasoning ?? null,
3156
+ input.confidence ?? 0.5,
3157
+ input.sourceSessionId ?? null,
3158
+ input.sourceType ?? "synthesized",
3159
+ now,
3160
+ now
3161
+ );
3162
+ return getLesson(id);
3163
+ }
3164
+ function updateLesson(id, updates) {
3165
+ const db2 = getDatabase();
3166
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3167
+ const fields = ["updated_at = ?"];
3168
+ const values = [now];
3169
+ if (updates.category !== void 0) {
3170
+ fields.push("category = ?");
3171
+ values.push(updates.category);
3172
+ }
3173
+ if (updates.title !== void 0) {
3174
+ fields.push("title = ?");
3175
+ values.push(updates.title);
3176
+ }
3177
+ if (updates.triggerContext !== void 0) {
3178
+ fields.push("trigger_context = ?");
3179
+ values.push(updates.triggerContext);
3180
+ }
3181
+ if (updates.insight !== void 0) {
3182
+ fields.push("insight = ?");
3183
+ values.push(updates.insight);
3184
+ }
3185
+ if (updates.reasoning !== void 0) {
3186
+ fields.push("reasoning = ?");
3187
+ values.push(updates.reasoning);
3188
+ }
3189
+ if (updates.confidence !== void 0) {
3190
+ fields.push("confidence = ?");
3191
+ values.push(updates.confidence);
3192
+ }
3193
+ if (updates.archived !== void 0) {
3194
+ fields.push("archived = ?");
3195
+ values.push(updates.archived ? 1 : 0);
3196
+ }
3197
+ values.push(id);
3198
+ db2.prepare(`
3199
+ UPDATE lessons SET ${fields.join(", ")} WHERE id = ?
3200
+ `).run(...values);
3201
+ return getLesson(id);
3202
+ }
3203
+ function deleteLesson(id) {
3204
+ const db2 = getDatabase();
3205
+ const transaction = db2.transaction(() => {
3206
+ db2.prepare("DELETE FROM lesson_embeddings WHERE lesson_id = ?").run(id);
3207
+ db2.prepare("DELETE FROM lesson_feedback WHERE lesson_id = ?").run(id);
3208
+ const result = db2.prepare("DELETE FROM lessons WHERE id = ?").run(id);
3209
+ return result.changes > 0;
3210
+ });
3211
+ return transaction();
3212
+ }
3213
+ function archiveLesson(id) {
3214
+ const db2 = getDatabase();
3215
+ db2.prepare(`
3216
+ UPDATE lessons SET archived = 1, updated_at = ? WHERE id = ?
3217
+ `).run((/* @__PURE__ */ new Date()).toISOString(), id);
3218
+ }
3219
+ function unarchiveLesson(id) {
3220
+ const db2 = getDatabase();
3221
+ db2.prepare(`
3222
+ UPDATE lessons SET archived = 0, updated_at = ? WHERE id = ?
3223
+ `).run((/* @__PURE__ */ new Date()).toISOString(), id);
3224
+ }
3225
+ function getLesson(id) {
3226
+ const db2 = getDatabase();
3227
+ const row = db2.prepare(`
3228
+ SELECT * FROM lessons WHERE id = ?
3229
+ `).get(id);
3230
+ return row ? mapLessonRow(row) : null;
3231
+ }
3232
+ function getLessonsByProject(projectPath, options = {}) {
3233
+ const db2 = getDatabase();
3234
+ let query = "SELECT * FROM lessons WHERE project_path = ?";
3235
+ const params = [projectPath];
3236
+ if (options.category) {
3237
+ query += " AND category = ?";
3238
+ params.push(options.category);
3239
+ }
3240
+ if (options.archived !== void 0) {
3241
+ query += " AND archived = ?";
3242
+ params.push(options.archived ? 1 : 0);
3243
+ } else {
3244
+ query += " AND archived = 0";
3245
+ }
3246
+ if (options.minConfidence !== void 0) {
3247
+ query += " AND confidence >= ?";
3248
+ params.push(options.minConfidence);
3249
+ }
3250
+ query += " ORDER BY confidence DESC, times_applied DESC";
3251
+ if (options.limit) {
3252
+ query += " LIMIT ?";
3253
+ params.push(options.limit);
3254
+ }
3255
+ const rows = db2.prepare(query).all(...params);
3256
+ return rows.map(mapLessonRow);
3257
+ }
3258
+ function getCoreLessons(projectPath, limit = 3) {
3259
+ const db2 = getDatabase();
3260
+ const rows = db2.prepare(`
3261
+ SELECT * FROM lessons
3262
+ WHERE project_path = ?
3263
+ AND archived = 0
3264
+ AND confidence >= 0.7
3265
+ ORDER BY
3266
+ times_validated DESC,
3267
+ confidence DESC,
3268
+ times_applied DESC
3269
+ LIMIT ?
3270
+ `).all(projectPath, limit);
3271
+ return rows.map(mapLessonRow);
3272
+ }
3273
+ function getAllLessons(options = {}) {
3274
+ const db2 = getDatabase();
3275
+ let query = "SELECT * FROM lessons WHERE 1=1";
3276
+ const params = [];
3277
+ if (options.category) {
3278
+ query += " AND category = ?";
3279
+ params.push(options.category);
3280
+ }
3281
+ if (options.archived !== void 0) {
3282
+ query += " AND archived = ?";
3283
+ params.push(options.archived ? 1 : 0);
3284
+ }
3285
+ if (options.minConfidence !== void 0) {
3286
+ query += " AND confidence >= ?";
3287
+ params.push(options.minConfidence);
3288
+ }
3289
+ query += " ORDER BY updated_at DESC";
3290
+ if (options.limit) {
3291
+ query += " LIMIT ?";
3292
+ params.push(options.limit);
3293
+ }
3294
+ const rows = db2.prepare(query).all(...params);
3295
+ return rows.map(mapLessonRow);
3296
+ }
3297
+ function recordLessonApplication(id) {
3298
+ const db2 = getDatabase();
3299
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3300
+ db2.prepare(`
3301
+ UPDATE lessons
3302
+ SET times_applied = times_applied + 1,
3303
+ last_applied_at = ?,
3304
+ updated_at = ?
3305
+ WHERE id = ?
3306
+ `).run(now, now, id);
3307
+ }
3308
+ function recordLessonValidation(id, sessionId, comment) {
3309
+ const db2 = getDatabase();
3310
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3311
+ const transaction = db2.transaction(() => {
3312
+ db2.prepare(`
3313
+ UPDATE lessons
3314
+ SET times_validated = times_validated + 1, updated_at = ?
3315
+ WHERE id = ?
3316
+ `).run(now, id);
3317
+ db2.prepare(`
3318
+ INSERT INTO lesson_feedback (lesson_id, session_id, feedback_type, comment)
3319
+ VALUES (?, ?, 'validated', ?)
3320
+ `).run(id, sessionId ?? null, comment ?? null);
3321
+ });
3322
+ transaction();
3323
+ }
3324
+ function recordLessonRejection(id, sessionId, comment) {
3325
+ const db2 = getDatabase();
3326
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3327
+ const transaction = db2.transaction(() => {
3328
+ db2.prepare(`
3329
+ UPDATE lessons
3330
+ SET times_rejected = times_rejected + 1, updated_at = ?
3331
+ WHERE id = ?
3332
+ `).run(now, id);
3333
+ db2.prepare(`
3334
+ INSERT INTO lesson_feedback (lesson_id, session_id, feedback_type, comment)
3335
+ VALUES (?, ?, 'rejected', ?)
3336
+ `).run(id, sessionId ?? null, comment ?? null);
3337
+ });
3338
+ transaction();
3339
+ }
3340
+ function storeLessonEmbedding(lessonId, embedding) {
3341
+ const db2 = getDatabase();
3342
+ db2.prepare("DELETE FROM lesson_embeddings WHERE lesson_id = ?").run(lessonId);
3343
+ db2.prepare(`
3344
+ INSERT INTO lesson_embeddings (lesson_id, embedding)
3345
+ VALUES (?, ?)
3346
+ `).run(lessonId, JSON.stringify(embedding));
3347
+ }
3348
+ function searchLessonsByEmbedding(embedding, projectPath, limit = 5) {
3349
+ return searchLessonsByEmbeddingWithDistance(embedding, projectPath, limit).map((r) => r.lesson);
3350
+ }
3351
+ function searchLessonsByEmbeddingWithDistance(embedding, projectPath, limit = 5) {
3352
+ const db2 = getDatabase();
3353
+ const rows = db2.prepare(`
3354
+ SELECT
3355
+ l.*,
3356
+ le.distance
3357
+ FROM lesson_embeddings le
3358
+ JOIN lessons l ON l.id = le.lesson_id
3359
+ WHERE l.project_path = ?
3360
+ AND l.archived = 0
3361
+ AND le.embedding MATCH ?
3362
+ ORDER BY le.distance ASC
3363
+ LIMIT ?
3364
+ `).all(projectPath, JSON.stringify(embedding), limit);
3365
+ return rows.map((row) => ({
3366
+ lesson: mapLessonRow(row),
3367
+ distance: row.distance
3368
+ }));
3369
+ }
3370
+ function getPendingSynthesis(limit = 5) {
3371
+ const db2 = getDatabase();
3372
+ const rows = db2.prepare(`
3373
+ SELECT
3374
+ id,
3375
+ session_id as sessionId,
3376
+ project_path as projectPath,
3377
+ queued_at as queuedAt,
3378
+ status,
3379
+ processed_at as processedAt,
3380
+ lessons_created as lessonsCreated,
3381
+ error
3382
+ FROM synthesis_queue
3383
+ WHERE status = 'pending'
3384
+ ORDER BY queued_at ASC
3385
+ LIMIT ?
3386
+ `).all(limit);
3387
+ return rows;
3388
+ }
3389
+ function markSynthesisProcessing(id) {
3390
+ const db2 = getDatabase();
3391
+ db2.prepare(`
3392
+ UPDATE synthesis_queue SET status = 'processing' WHERE id = ?
3393
+ `).run(id);
3394
+ }
3395
+ function markSynthesisComplete(id, lessonsCreated) {
3396
+ const db2 = getDatabase();
3397
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3398
+ db2.prepare(`
3399
+ UPDATE synthesis_queue
3400
+ SET status = 'completed', processed_at = ?, lessons_created = ?
3401
+ WHERE id = ?
3402
+ `).run(now, lessonsCreated, id);
3403
+ }
3404
+ function markSynthesisFailed(id, error) {
3405
+ const db2 = getDatabase();
3406
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3407
+ db2.prepare(`
3408
+ UPDATE synthesis_queue
3409
+ SET status = 'failed', processed_at = ?, error = ?
3410
+ WHERE id = ?
3411
+ `).run(now, error, id);
3412
+ }
3413
+ function getSynthesisStats() {
3414
+ const db2 = getDatabase();
3415
+ const pending = db2.prepare(`
3416
+ SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'pending'
3417
+ `).get().count;
3418
+ const processing = db2.prepare(`
3419
+ SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'processing'
3420
+ `).get().count;
3421
+ const completed = db2.prepare(`
3422
+ SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'completed'
3423
+ `).get().count;
3424
+ const failed = db2.prepare(`
3425
+ SELECT COUNT(*) as count FROM synthesis_queue WHERE status = 'failed'
3426
+ `).get().count;
3427
+ const totalLessonsCreated = db2.prepare(`
3428
+ SELECT COALESCE(SUM(lessons_created), 0) as total FROM synthesis_queue WHERE status = 'completed'
3429
+ `).get().total;
3430
+ return { pending, processing, completed, failed, totalLessonsCreated };
3431
+ }
3432
+ function getLessonStats() {
3433
+ const db2 = getDatabase();
3434
+ const totalLessons = db2.prepare(`
3435
+ SELECT COUNT(*) as count FROM lessons
3436
+ `).get().count;
3437
+ const activeLessons = db2.prepare(`
3438
+ SELECT COUNT(*) as count FROM lessons WHERE archived = 0
3439
+ `).get().count;
3440
+ const archivedLessons = totalLessons - activeLessons;
3441
+ const categoryRows = db2.prepare(`
3442
+ SELECT category, COUNT(*) as count
3443
+ FROM lessons
3444
+ WHERE archived = 0
3445
+ GROUP BY category
3446
+ `).all();
3447
+ const byCategory = {
3448
+ architecture_decision: 0,
3449
+ anti_pattern: 0,
3450
+ bug_pattern: 0,
3451
+ project_convention: 0,
3452
+ dependency_knowledge: 0,
3453
+ domain_knowledge: 0,
3454
+ workflow: 0,
3455
+ other: 0
3456
+ };
3457
+ for (const row of categoryRows) {
3458
+ byCategory[row.category] = row.count;
3459
+ }
3460
+ const avgConfidence = db2.prepare(`
3461
+ SELECT COALESCE(AVG(confidence), 0) as avg FROM lessons WHERE archived = 0
3462
+ `).get().avg;
3463
+ return {
3464
+ totalLessons,
3465
+ activeLessons,
3466
+ archivedLessons,
3467
+ byCategory,
3468
+ avgConfidence
3469
+ };
3470
+ }
3471
+ function getQueueStatus() {
3472
+ return getSynthesisStats();
3473
+ }
3474
+ function cleanupOldInjections(daysOld = 7) {
3475
+ const db2 = getDatabase();
3476
+ const cutoff = /* @__PURE__ */ new Date();
3477
+ cutoff.setDate(cutoff.getDate() - daysOld);
3478
+ const result = db2.prepare(`
3479
+ DELETE FROM session_injections WHERE injected_at < ?
3480
+ `).run(cutoff.toISOString());
3481
+ return result.changes;
3482
+ }
3483
+ function getDistinctProjects() {
3484
+ const db2 = getDatabase();
3485
+ const rows = db2.prepare(`
3486
+ SELECT DISTINCT project_path
3487
+ FROM lessons
3488
+ WHERE project_path IS NOT NULL AND project_path != ''
3489
+ ORDER BY project_path ASC
3490
+ `).all();
3491
+ return rows.map((row) => row.project_path);
3492
+ }
3493
+
3494
+ // src/learning/LessonManager.ts
3495
+ var VALIDATION_BOOST = 0.1;
3496
+ var REJECTION_PENALTY = 0.2;
3497
+ var MIN_CONFIDENCE = 0;
3498
+ var MAX_CONFIDENCE = 1;
3499
+ var AUTO_ARCHIVE_THRESHOLD = 0.1;
3500
+ var DECAY_RATE = 0.05;
3501
+ var LessonManager = class {
3502
+ /**
3503
+ * Create a new lesson and generate its embedding
3504
+ */
3505
+ async create(input) {
3506
+ const lesson = createLesson(input);
3507
+ try {
3508
+ const embeddingText = this.buildEmbeddingText(lesson);
3509
+ const embedding = await getEmbedding(embeddingText);
3510
+ storeLessonEmbedding(lesson.id, embedding);
3511
+ } catch {
3512
+ }
3513
+ return lesson;
3514
+ }
3515
+ /**
3516
+ * Update a lesson
3517
+ */
3518
+ update(id, updates) {
3519
+ return updateLesson(id, updates);
3520
+ }
3521
+ /**
3522
+ * Update a lesson and regenerate its embedding
3523
+ */
3524
+ async updateWithEmbedding(id, updates) {
3525
+ const lesson = updateLesson(id, updates);
3526
+ if (lesson) {
3527
+ if (updates.title || updates.triggerContext || updates.insight) {
3528
+ try {
3529
+ const embeddingText = this.buildEmbeddingText(lesson);
3530
+ const embedding = await getEmbedding(embeddingText);
3531
+ storeLessonEmbedding(lesson.id, embedding);
3532
+ } catch {
3533
+ }
3534
+ }
3535
+ }
3536
+ return lesson;
3537
+ }
3538
+ /**
3539
+ * Delete a lesson permanently
3540
+ */
3541
+ delete(id) {
3542
+ return deleteLesson(id);
3543
+ }
3544
+ /**
3545
+ * Archive a lesson (soft delete)
3546
+ */
3547
+ archive(id) {
3548
+ archiveLesson(id);
3549
+ }
3550
+ /**
3551
+ * Unarchive a lesson
3552
+ */
3553
+ unarchive(id) {
3554
+ unarchiveLesson(id);
3555
+ }
3556
+ /**
3557
+ * Get a lesson by ID
3558
+ */
3559
+ get(id) {
3560
+ return getLesson(id);
3561
+ }
3562
+ /**
3563
+ * Get lessons for a project
3564
+ */
3565
+ getByProject(projectPath, options) {
3566
+ return getLessonsByProject(projectPath, options);
3567
+ }
3568
+ /**
3569
+ * Get core lessons (high confidence, well-validated)
3570
+ */
3571
+ getCore(projectPath, limit) {
3572
+ return getCoreLessons(projectPath, limit);
3573
+ }
3574
+ /**
3575
+ * Get all lessons
3576
+ */
3577
+ getAll(options) {
3578
+ return getAllLessons(options);
3579
+ }
3580
+ /**
3581
+ * Record that a lesson was applied (shown to user)
3582
+ */
3583
+ recordApplication(id) {
3584
+ recordLessonApplication(id);
3585
+ }
3586
+ /**
3587
+ * Record validation feedback - boosts confidence
3588
+ */
3589
+ recordValidation(id, sessionId, comment) {
3590
+ recordLessonValidation(id, sessionId, comment);
3591
+ const lesson = getLesson(id);
3592
+ if (lesson) {
3593
+ const newConfidence = Math.min(MAX_CONFIDENCE, lesson.confidence + VALIDATION_BOOST);
3594
+ updateLesson(id, { confidence: newConfidence });
3595
+ }
3596
+ }
3597
+ /**
3598
+ * Record rejection feedback - reduces confidence
3599
+ * Auto-archives if confidence drops too low
3600
+ */
3601
+ recordRejection(id, sessionId, comment) {
3602
+ recordLessonRejection(id, sessionId, comment);
3603
+ const lesson = getLesson(id);
3604
+ if (lesson) {
3605
+ const newConfidence = Math.max(MIN_CONFIDENCE, lesson.confidence - REJECTION_PENALTY);
3606
+ if (newConfidence < AUTO_ARCHIVE_THRESHOLD) {
3607
+ archiveLesson(id);
3608
+ } else {
3609
+ updateLesson(id, { confidence: newConfidence });
3610
+ }
3611
+ }
3612
+ }
3613
+ /**
3614
+ * Decay confidence of unused lessons
3615
+ * Should be run periodically (e.g., weekly)
3616
+ */
3617
+ decayUnusedLessons(daysThreshold = 30) {
3618
+ const lessons = getAllLessons({ archived: false });
3619
+ const cutoffDate = /* @__PURE__ */ new Date();
3620
+ cutoffDate.setDate(cutoffDate.getDate() - daysThreshold);
3621
+ let decayedCount = 0;
3622
+ for (const lesson of lessons) {
3623
+ const lastUsed = lesson.lastAppliedAt ? new Date(lesson.lastAppliedAt) : new Date(lesson.createdAt);
3624
+ if (lastUsed < cutoffDate) {
3625
+ const newConfidence = Math.max(MIN_CONFIDENCE, lesson.confidence - DECAY_RATE);
3626
+ if (newConfidence < AUTO_ARCHIVE_THRESHOLD) {
3627
+ archiveLesson(lesson.id);
3628
+ } else {
3629
+ updateLesson(lesson.id, { confidence: newConfidence });
3630
+ }
3631
+ decayedCount++;
3632
+ }
3633
+ }
3634
+ return decayedCount;
3635
+ }
3636
+ /**
3637
+ * Build text for embedding generation
3638
+ */
3639
+ buildEmbeddingText(lesson) {
3640
+ const parts = [
3641
+ `Title: ${lesson.title}`,
3642
+ `Category: ${lesson.category}`,
3643
+ `When to apply: ${lesson.triggerContext}`,
3644
+ `Insight: ${lesson.insight}`
3645
+ ];
3646
+ if (lesson.reasoning) {
3647
+ parts.push(`Reasoning: ${lesson.reasoning}`);
3648
+ }
3649
+ return parts.join("\n\n");
3650
+ }
3651
+ };
3652
+ var lessonManager = new LessonManager();
3653
+
3654
+ // src/learning/types.ts
3655
+ var VALID_CATEGORIES = [
3656
+ "architecture_decision",
3657
+ "anti_pattern",
3658
+ "bug_pattern",
3659
+ "project_convention",
3660
+ "dependency_knowledge",
3661
+ "domain_knowledge",
3662
+ "workflow",
3663
+ "other"
3664
+ ];
3665
+
3217
3666
  // src/mcp/server.ts
3218
3667
  function createMcpServer() {
3219
3668
  const server = new Server(
@@ -3323,29 +3772,187 @@ function createMcpServer() {
3323
3772
  },
3324
3773
  required: ["query"]
3325
3774
  }
3326
- }
3327
- ]
3328
- };
3329
- });
3330
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
3331
- const { name, arguments: args } = request.params;
3332
- try {
3333
- switch (name) {
3334
- case "search_sessions":
3335
- return await handleSearchSessions(args);
3336
- case "list_sessions":
3337
- return await handleListSessions(args);
3338
- case "get_session":
3339
- return await handleGetSession(
3340
- args
3341
- );
3342
- case "get_session_context":
3343
- return await handleGetSessionContext(
3344
- args
3345
- );
3346
- case "search_and_summarize":
3347
- return await handleSearchAndSummarize(
3348
- args
3775
+ },
3776
+ // ==================== LESSON TOOLS ====================
3777
+ {
3778
+ name: "search_lessons",
3779
+ description: "Search learned lessons for a project using semantic search. Returns lessons that match the query context.",
3780
+ inputSchema: {
3781
+ type: "object",
3782
+ properties: {
3783
+ query: {
3784
+ type: "string",
3785
+ description: "Search query describing what you want to find"
3786
+ },
3787
+ projectPath: {
3788
+ type: "string",
3789
+ description: "Project path to search lessons for (defaults to current directory)"
3790
+ },
3791
+ limit: {
3792
+ type: "number",
3793
+ description: "Maximum number of results (default: 5)"
3794
+ }
3795
+ },
3796
+ required: ["query"]
3797
+ }
3798
+ },
3799
+ {
3800
+ name: "get_lesson",
3801
+ description: "Get details of a specific learned lesson by ID.",
3802
+ inputSchema: {
3803
+ type: "object",
3804
+ properties: {
3805
+ lessonId: {
3806
+ type: "string",
3807
+ description: "The lesson ID to retrieve"
3808
+ }
3809
+ },
3810
+ required: ["lessonId"]
3811
+ }
3812
+ },
3813
+ {
3814
+ name: "save_lesson",
3815
+ description: "Manually save a new lesson. Use this when you learn something important about a project that should be remembered.",
3816
+ inputSchema: {
3817
+ type: "object",
3818
+ properties: {
3819
+ projectPath: {
3820
+ type: "string",
3821
+ description: "Project path this lesson applies to"
3822
+ },
3823
+ category: {
3824
+ type: "string",
3825
+ enum: VALID_CATEGORIES,
3826
+ description: "Category of the lesson"
3827
+ },
3828
+ title: {
3829
+ type: "string",
3830
+ description: "Short title (max 60 chars)"
3831
+ },
3832
+ triggerContext: {
3833
+ type: "string",
3834
+ description: "When this lesson should be surfaced"
3835
+ },
3836
+ insight: {
3837
+ type: "string",
3838
+ description: "The actual knowledge (specific, actionable)"
3839
+ },
3840
+ reasoning: {
3841
+ type: "string",
3842
+ description: "Why this is true (optional)"
3843
+ }
3844
+ },
3845
+ required: ["projectPath", "category", "title", "triggerContext", "insight"]
3846
+ }
3847
+ },
3848
+ {
3849
+ name: "validate_lesson",
3850
+ description: "Mark a lesson as helpful/correct. Boosts its confidence score.",
3851
+ inputSchema: {
3852
+ type: "object",
3853
+ properties: {
3854
+ lessonId: {
3855
+ type: "string",
3856
+ description: "The lesson ID to validate"
3857
+ },
3858
+ comment: {
3859
+ type: "string",
3860
+ description: "Optional comment about why it was helpful"
3861
+ }
3862
+ },
3863
+ required: ["lessonId"]
3864
+ }
3865
+ },
3866
+ {
3867
+ name: "reject_lesson",
3868
+ description: "Mark a lesson as unhelpful/incorrect. Reduces its confidence score.",
3869
+ inputSchema: {
3870
+ type: "object",
3871
+ properties: {
3872
+ lessonId: {
3873
+ type: "string",
3874
+ description: "The lesson ID to reject"
3875
+ },
3876
+ comment: {
3877
+ type: "string",
3878
+ description: "Optional comment about why it was wrong"
3879
+ }
3880
+ },
3881
+ required: ["lessonId"]
3882
+ }
3883
+ },
3884
+ {
3885
+ name: "list_lessons",
3886
+ description: "List all lessons for a project.",
3887
+ inputSchema: {
3888
+ type: "object",
3889
+ properties: {
3890
+ projectPath: {
3891
+ type: "string",
3892
+ description: "Project path to list lessons for"
3893
+ },
3894
+ category: {
3895
+ type: "string",
3896
+ enum: VALID_CATEGORIES,
3897
+ description: "Filter by category"
3898
+ },
3899
+ includeArchived: {
3900
+ type: "boolean",
3901
+ description: "Include archived lessons (default: false)"
3902
+ },
3903
+ limit: {
3904
+ type: "number",
3905
+ description: "Maximum number of results (default: 20)"
3906
+ }
3907
+ },
3908
+ required: ["projectPath"]
3909
+ }
3910
+ }
3911
+ ]
3912
+ };
3913
+ });
3914
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3915
+ const { name, arguments: args } = request.params;
3916
+ try {
3917
+ switch (name) {
3918
+ case "search_sessions":
3919
+ return await handleSearchSessions(args);
3920
+ case "list_sessions":
3921
+ return await handleListSessions(args);
3922
+ case "get_session":
3923
+ return await handleGetSession(
3924
+ args
3925
+ );
3926
+ case "get_session_context":
3927
+ return await handleGetSessionContext(
3928
+ args
3929
+ );
3930
+ case "search_and_summarize":
3931
+ return await handleSearchAndSummarize(
3932
+ args
3933
+ );
3934
+ // ==================== LESSON HANDLERS ====================
3935
+ case "search_lessons":
3936
+ return await handleSearchLessons(
3937
+ args
3938
+ );
3939
+ case "get_lesson":
3940
+ return await handleGetLesson(args);
3941
+ case "save_lesson":
3942
+ return await handleSaveLesson(
3943
+ args
3944
+ );
3945
+ case "validate_lesson":
3946
+ return await handleValidateLesson(
3947
+ args
3948
+ );
3949
+ case "reject_lesson":
3950
+ return await handleRejectLesson(
3951
+ args
3952
+ );
3953
+ case "list_lessons":
3954
+ return await handleListLessons(
3955
+ args
3349
3956
  );
3350
3957
  default:
3351
3958
  return {
@@ -3609,6 +4216,210 @@ No sessions found.`;
3609
4216
  }
3610
4217
  return lines.join("\n");
3611
4218
  }
4219
+ async function handleSearchLessons(args) {
4220
+ const { query, projectPath = process.cwd(), limit = 5 } = args;
4221
+ if (!isReady()) {
4222
+ try {
4223
+ await initializeEmbeddings();
4224
+ } catch {
4225
+ return {
4226
+ content: [
4227
+ {
4228
+ type: "text",
4229
+ text: "Embedding model not available. Cannot perform semantic search."
4230
+ }
4231
+ ],
4232
+ isError: true
4233
+ };
4234
+ }
4235
+ }
4236
+ const queryEmbedding = await getEmbedding(query);
4237
+ const lessons = searchLessonsByEmbedding(queryEmbedding, projectPath, limit);
4238
+ if (lessons.length === 0) {
4239
+ return {
4240
+ content: [
4241
+ {
4242
+ type: "text",
4243
+ text: `No lessons found for query: "${query}" in project: ${projectPath}`
4244
+ }
4245
+ ]
4246
+ };
4247
+ }
4248
+ const lines = [`# Lessons matching "${query}"`, ""];
4249
+ for (const lesson of lessons) {
4250
+ lines.push(`### ${lesson.title}`);
4251
+ lines.push(`- **ID:** \`${lesson.id.slice(0, 8)}\``);
4252
+ lines.push(`- **Category:** ${lesson.category}`);
4253
+ lines.push(`- **Confidence:** ${(lesson.confidence * 100).toFixed(0)}%`);
4254
+ lines.push(`- **When to apply:** ${lesson.triggerContext}`);
4255
+ lines.push("");
4256
+ lines.push(`**Insight:** ${lesson.insight}`);
4257
+ if (lesson.reasoning) {
4258
+ lines.push(`**Reasoning:** ${lesson.reasoning}`);
4259
+ }
4260
+ lines.push("");
4261
+ }
4262
+ return {
4263
+ content: [{ type: "text", text: lines.join("\n") }]
4264
+ };
4265
+ }
4266
+ async function handleGetLesson(args) {
4267
+ const { lessonId } = args;
4268
+ const lesson = getLesson(lessonId);
4269
+ if (!lesson) {
4270
+ return {
4271
+ content: [{ type: "text", text: `Lesson not found: ${lessonId}` }],
4272
+ isError: true
4273
+ };
4274
+ }
4275
+ const lines = [
4276
+ `# ${lesson.title}`,
4277
+ "",
4278
+ `**ID:** ${lesson.id}`,
4279
+ `**Category:** ${lesson.category}`,
4280
+ `**Project:** ${lesson.projectPath}`,
4281
+ `**Confidence:** ${(lesson.confidence * 100).toFixed(0)}%`,
4282
+ `**Times Applied:** ${lesson.timesApplied}`,
4283
+ `**Times Validated:** ${lesson.timesValidated}`,
4284
+ `**Times Rejected:** ${lesson.timesRejected}`,
4285
+ `**Archived:** ${lesson.archived ? "Yes" : "No"}`,
4286
+ `**Created:** ${lesson.createdAt}`,
4287
+ `**Updated:** ${lesson.updatedAt}`,
4288
+ "",
4289
+ "## When to Apply",
4290
+ lesson.triggerContext,
4291
+ "",
4292
+ "## Insight",
4293
+ lesson.insight
4294
+ ];
4295
+ if (lesson.reasoning) {
4296
+ lines.push("", "## Reasoning", lesson.reasoning);
4297
+ }
4298
+ if (lesson.sourceSessionId) {
4299
+ lines.push("", `**Source Session:** ${lesson.sourceSessionId}`);
4300
+ }
4301
+ return {
4302
+ content: [{ type: "text", text: lines.join("\n") }]
4303
+ };
4304
+ }
4305
+ async function handleSaveLesson(args) {
4306
+ const input = {
4307
+ projectPath: args.projectPath,
4308
+ category: args.category,
4309
+ title: args.title,
4310
+ triggerContext: args.triggerContext,
4311
+ insight: args.insight,
4312
+ reasoning: args.reasoning,
4313
+ sourceType: "manual",
4314
+ confidence: 0.6
4315
+ // Manual lessons start with moderate confidence
4316
+ };
4317
+ try {
4318
+ const lesson = await lessonManager.create(input);
4319
+ return {
4320
+ content: [
4321
+ {
4322
+ type: "text",
4323
+ text: `Lesson saved successfully!
4324
+
4325
+ **ID:** ${lesson.id}
4326
+ **Title:** ${lesson.title}`
4327
+ }
4328
+ ]
4329
+ };
4330
+ } catch (error) {
4331
+ return {
4332
+ content: [
4333
+ {
4334
+ type: "text",
4335
+ text: `Failed to save lesson: ${String(error)}`
4336
+ }
4337
+ ],
4338
+ isError: true
4339
+ };
4340
+ }
4341
+ }
4342
+ async function handleValidateLesson(args) {
4343
+ const { lessonId, comment } = args;
4344
+ const lesson = getLesson(lessonId);
4345
+ if (!lesson) {
4346
+ return {
4347
+ content: [{ type: "text", text: `Lesson not found: ${lessonId}` }],
4348
+ isError: true
4349
+ };
4350
+ }
4351
+ lessonManager.recordValidation(lessonId, void 0, comment);
4352
+ const updated = getLesson(lessonId);
4353
+ return {
4354
+ content: [
4355
+ {
4356
+ type: "text",
4357
+ text: `Lesson validated: "${lesson.title}"
4358
+ New confidence: ${((updated?.confidence ?? 0) * 100).toFixed(0)}%`
4359
+ }
4360
+ ]
4361
+ };
4362
+ }
4363
+ async function handleRejectLesson(args) {
4364
+ const { lessonId, comment } = args;
4365
+ const lesson = getLesson(lessonId);
4366
+ if (!lesson) {
4367
+ return {
4368
+ content: [{ type: "text", text: `Lesson not found: ${lessonId}` }],
4369
+ isError: true
4370
+ };
4371
+ }
4372
+ lessonManager.recordRejection(lessonId, void 0, comment);
4373
+ const updated = getLesson(lessonId);
4374
+ let message = `Lesson rejected: "${lesson.title}"`;
4375
+ if (updated?.archived) {
4376
+ message += "\nLesson has been auto-archived due to low confidence.";
4377
+ } else if (updated) {
4378
+ message += `
4379
+ New confidence: ${(updated.confidence * 100).toFixed(0)}%`;
4380
+ }
4381
+ return {
4382
+ content: [{ type: "text", text: message }]
4383
+ };
4384
+ }
4385
+ async function handleListLessons(args) {
4386
+ const { projectPath, category, includeArchived = false, limit = 20 } = args;
4387
+ const lessons = getLessonsByProject(projectPath, {
4388
+ category,
4389
+ archived: includeArchived ? void 0 : false,
4390
+ limit
4391
+ });
4392
+ if (lessons.length === 0) {
4393
+ return {
4394
+ content: [
4395
+ {
4396
+ type: "text",
4397
+ text: `No lessons found for project: ${projectPath}`
4398
+ }
4399
+ ]
4400
+ };
4401
+ }
4402
+ const stats = getLessonStats();
4403
+ const lines = [
4404
+ `# Lessons for ${projectPath}`,
4405
+ "",
4406
+ `**Total:** ${lessons.length} | **Active:** ${stats.activeLessons} | **Archived:** ${stats.archivedLessons}`,
4407
+ ""
4408
+ ];
4409
+ for (const lesson of lessons) {
4410
+ const archived = lesson.archived ? " [ARCHIVED]" : "";
4411
+ lines.push(`### ${lesson.title}${archived}`);
4412
+ lines.push(`- **ID:** \`${lesson.id.slice(0, 8)}\``);
4413
+ lines.push(`- **Category:** ${lesson.category}`);
4414
+ lines.push(`- **Confidence:** ${(lesson.confidence * 100).toFixed(0)}%`);
4415
+ lines.push(`- **Applied:** ${lesson.timesApplied} | **Validated:** ${lesson.timesValidated} | **Rejected:** ${lesson.timesRejected}`);
4416
+ lines.push(`- **Insight:** ${truncate(lesson.insight, 100)}`);
4417
+ lines.push("");
4418
+ }
4419
+ return {
4420
+ content: [{ type: "text", text: lines.join("\n") }]
4421
+ };
4422
+ }
3612
4423
  async function startMcpServer() {
3613
4424
  const server = createMcpServer();
3614
4425
  const transport = new StdioServerTransport();
@@ -3626,29 +4437,32 @@ async function mcpCommand() {
3626
4437
  }
3627
4438
  }
3628
4439
 
3629
- // src/cli.ts
3630
- init_install();
3631
-
3632
4440
  // src/commands/setup.ts
3633
- import chalk10 from "chalk";
3634
- import { execSync as execSync3 } from "child_process";
3635
- import { existsSync as existsSync11, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3636
- import { homedir as homedir3 } from "os";
3637
- import { join as join7, dirname as dirname7, basename as basename7 } from "path";
4441
+ import chalk9 from "chalk";
4442
+ import { execSync as execSync2 } from "child_process";
4443
+ import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
4444
+ import { homedir as homedir2 } from "os";
4445
+ import { join as join6, dirname as dirname6, basename as basename7 } from "path";
3638
4446
  import { fileURLToPath } from "url";
3639
4447
  import { createInterface } from "readline";
3640
4448
  var __filename = fileURLToPath(import.meta.url);
3641
- var __dirname = dirname7(__filename);
3642
- var CLAUDE_JSON_PATH = join7(homedir3(), ".claude.json");
3643
- var CLAUDE_SETTINGS_PATH = join7(homedir3(), ".claude", "settings.json");
3644
- var CMEM_DIR2 = join7(homedir3(), ".cmem");
3645
- var SETUP_MARKER = join7(CMEM_DIR2, ".setup-complete");
4449
+ var __dirname = dirname6(__filename);
4450
+ var CLAUDE_JSON_PATH = join6(homedir2(), ".claude.json");
4451
+ var CLAUDE_SETTINGS_PATH = join6(homedir2(), ".claude", "settings.json");
4452
+ var CMEM_DIR2 = join6(homedir2(), ".cmem");
4453
+ var SETUP_MARKER = join6(CMEM_DIR2, ".setup-complete");
3646
4454
  var CMEM_PERMISSIONS = [
3647
4455
  "mcp__cmem__search_sessions",
3648
4456
  "mcp__cmem__list_sessions",
3649
4457
  "mcp__cmem__get_session",
3650
4458
  "mcp__cmem__get_session_context",
3651
- "mcp__cmem__search_and_summarize"
4459
+ "mcp__cmem__search_and_summarize",
4460
+ "mcp__cmem__search_lessons",
4461
+ "mcp__cmem__get_lesson",
4462
+ "mcp__cmem__save_lesson",
4463
+ "mcp__cmem__validate_lesson",
4464
+ "mcp__cmem__reject_lesson",
4465
+ "mcp__cmem__list_lessons"
3652
4466
  ];
3653
4467
  var spinnerFrames2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3654
4468
  var Spinner3 = class {
@@ -3662,24 +4476,24 @@ var Spinner3 = class {
3662
4476
  process.stdout.write(` ${spinnerFrames2[0]} ${this.message}`);
3663
4477
  this.interval = setInterval(() => {
3664
4478
  this.frameIndex = (this.frameIndex + 1) % spinnerFrames2.length;
3665
- process.stdout.write(`\r ${chalk10.cyan(spinnerFrames2[this.frameIndex])} ${this.message}`);
4479
+ process.stdout.write(`\r ${chalk9.cyan(spinnerFrames2[this.frameIndex])} ${this.message}`);
3666
4480
  }, 80);
3667
4481
  }
3668
4482
  update(message) {
3669
4483
  this.message = message;
3670
- process.stdout.write(`\r ${chalk10.cyan(spinnerFrames2[this.frameIndex])} ${this.message} `);
4484
+ process.stdout.write(`\r ${chalk9.cyan(spinnerFrames2[this.frameIndex])} ${this.message} `);
3671
4485
  }
3672
4486
  succeed(message) {
3673
4487
  this.stop();
3674
- console.log(`\r ${chalk10.green("\u2713")} ${message || this.message} `);
4488
+ console.log(`\r ${chalk9.green("\u2713")} ${message || this.message} `);
3675
4489
  }
3676
4490
  fail(message) {
3677
4491
  this.stop();
3678
- console.log(`\r ${chalk10.red("\u2717")} ${message || this.message} `);
4492
+ console.log(`\r ${chalk9.red("\u2717")} ${message || this.message} `);
3679
4493
  }
3680
4494
  warn(message) {
3681
4495
  this.stop();
3682
- console.log(`\r ${chalk10.yellow("!")} ${message || this.message} `);
4496
+ console.log(`\r ${chalk9.yellow("!")} ${message || this.message} `);
3683
4497
  }
3684
4498
  stop() {
3685
4499
  if (this.interval) {
@@ -3689,9 +4503,9 @@ var Spinner3 = class {
3689
4503
  }
3690
4504
  };
3691
4505
  function printBanner(version) {
3692
- const magenta = chalk10.magenta;
3693
- const cyan = chalk10.cyan;
3694
- const dim = chalk10.dim;
4506
+ const magenta = chalk9.magenta;
4507
+ const cyan = chalk9.cyan;
4508
+ const dim = chalk9.dim;
3695
4509
  console.log("");
3696
4510
  console.log(magenta(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557"));
3697
4511
  console.log(magenta(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551"));
@@ -3711,11 +4525,11 @@ async function promptChoice(question, options, defaultChoice = 1) {
3711
4525
  output: process.stdout
3712
4526
  });
3713
4527
  options.forEach((opt, i) => {
3714
- console.log(chalk10.dim(` ${i + 1}) ${opt}`));
4528
+ console.log(chalk9.dim(` ${i + 1}) ${opt}`));
3715
4529
  });
3716
4530
  console.log("");
3717
4531
  return new Promise((resolve) => {
3718
- rl.question(chalk10.white(` Choice [${defaultChoice}]: `), (answer) => {
4532
+ rl.question(chalk9.white(` Choice [${defaultChoice}]: `), (answer) => {
3719
4533
  rl.close();
3720
4534
  const num = parseInt(answer.trim(), 10);
3721
4535
  if (isNaN(num) || num < 1 || num > options.length) {
@@ -3733,7 +4547,7 @@ async function promptYesNo(question, defaultYes = true) {
3733
4547
  });
3734
4548
  const hint = defaultYes ? "[Y/n]" : "[y/N]";
3735
4549
  return new Promise((resolve) => {
3736
- rl.question(chalk10.white(` ${question} ${chalk10.dim(hint)} `), (answer) => {
4550
+ rl.question(chalk9.white(` ${question} ${chalk9.dim(hint)} `), (answer) => {
3737
4551
  rl.close();
3738
4552
  const normalized = answer.toLowerCase().trim();
3739
4553
  if (normalized === "") {
@@ -3750,7 +4564,7 @@ function isRunningViaNpx() {
3750
4564
  }
3751
4565
  function isGloballyInstalled() {
3752
4566
  try {
3753
- const result = execSync3("which cmem 2>/dev/null || where cmem 2>/dev/null", {
4567
+ const result = execSync2("which cmem 2>/dev/null || where cmem 2>/dev/null", {
3754
4568
  encoding: "utf-8",
3755
4569
  stdio: ["pipe", "pipe", "pipe"]
3756
4570
  }).trim();
@@ -3761,8 +4575,8 @@ function isGloballyInstalled() {
3761
4575
  }
3762
4576
  function getCmemVersion() {
3763
4577
  try {
3764
- const packagePath = join7(__dirname, "..", "package.json");
3765
- if (existsSync11(packagePath)) {
4578
+ const packagePath = join6(__dirname, "..", "package.json");
4579
+ if (existsSync10(packagePath)) {
3766
4580
  const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
3767
4581
  return pkg.version || "0.1.0";
3768
4582
  }
@@ -3772,7 +4586,7 @@ function getCmemVersion() {
3772
4586
  }
3773
4587
  function getInstalledVersion() {
3774
4588
  try {
3775
- const result = execSync3("cmem --version 2>/dev/null", {
4589
+ const result = execSync2("cmem --version 2>/dev/null", {
3776
4590
  encoding: "utf-8"
3777
4591
  }).trim();
3778
4592
  return result;
@@ -3780,49 +4594,86 @@ function getInstalledVersion() {
3780
4594
  return null;
3781
4595
  }
3782
4596
  }
4597
+ function getCmemDistPath() {
4598
+ try {
4599
+ const globalPath = execSync2("npm root -g 2>/dev/null", {
4600
+ encoding: "utf-8"
4601
+ }).trim();
4602
+ const cmemGlobalPath = join6(globalPath, "@colbymchenry", "cmem", "dist");
4603
+ if (existsSync10(cmemGlobalPath)) {
4604
+ return cmemGlobalPath;
4605
+ }
4606
+ const parentDist = dirname6(__dirname);
4607
+ if (existsSync10(join6(parentDist, "hooks"))) {
4608
+ return parentDist;
4609
+ }
4610
+ return null;
4611
+ } catch {
4612
+ const parentDist = dirname6(__dirname);
4613
+ if (existsSync10(parentDist)) {
4614
+ return parentDist;
4615
+ }
4616
+ return null;
4617
+ }
4618
+ }
4619
+ function cleanupOldDaemon() {
4620
+ const plistPath = join6(homedir2(), "Library", "LaunchAgents", "com.cmem.watch.plist");
4621
+ if (existsSync10(plistPath)) {
4622
+ try {
4623
+ execSync2(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "pipe" });
4624
+ } catch {
4625
+ }
4626
+ try {
4627
+ const { unlinkSync: unlinkSync2 } = __require("fs");
4628
+ unlinkSync2(plistPath);
4629
+ } catch {
4630
+ }
4631
+ }
4632
+ }
3783
4633
  async function setupCommand() {
3784
4634
  const currentVersion = getCmemVersion();
3785
4635
  const installedVersion = getInstalledVersion();
3786
4636
  const isGlobal = isGloballyInstalled();
3787
4637
  const isNpx = isRunningViaNpx();
3788
4638
  printBanner(currentVersion);
4639
+ cleanupOldDaemon();
3789
4640
  if (!isGlobal || isNpx) {
3790
- console.log(chalk10.yellow(" Install cmem globally?"));
3791
- console.log(chalk10.dim(" Makes the `cmem` command available everywhere\n"));
4641
+ console.log(chalk9.yellow(" Install cmem globally?"));
4642
+ console.log(chalk9.dim(" Makes the `cmem` command available everywhere\n"));
3792
4643
  const choice = await promptChoice("", [
3793
4644
  "Yes - install globally via npm",
3794
4645
  "No - I'll use npx each time"
3795
4646
  ], 1);
3796
4647
  if (choice === 1) {
3797
- console.log(chalk10.dim("\n Installing @colbymchenry/cmem globally...\n"));
4648
+ console.log(chalk9.dim("\n Installing @colbymchenry/cmem globally...\n"));
3798
4649
  try {
3799
- execSync3("npm install -g @colbymchenry/cmem", { stdio: "inherit" });
3800
- console.log(chalk10.green("\n \u2713 Installed globally\n"));
4650
+ execSync2("npm install -g @colbymchenry/cmem", { stdio: "inherit" });
4651
+ console.log(chalk9.green("\n \u2713 Installed globally\n"));
3801
4652
  } catch {
3802
- console.log(chalk10.red("\n \u2717 Failed to install. Try: sudo npm install -g @colbymchenry/cmem\n"));
4653
+ console.log(chalk9.red("\n \u2717 Failed to install. Try: sudo npm install -g @colbymchenry/cmem\n"));
3803
4654
  }
3804
4655
  } else {
3805
- console.log(chalk10.dim("\n Skipped. Use `npx @colbymchenry/cmem` to run.\n"));
4656
+ console.log(chalk9.dim("\n Skipped. Use `npx @colbymchenry/cmem` to run.\n"));
3806
4657
  }
3807
4658
  } else if (installedVersion && installedVersion !== currentVersion) {
3808
- console.log(chalk10.yellow(` Update available: ${installedVersion} \u2192 ${currentVersion}`));
4659
+ console.log(chalk9.yellow(` Update available: ${installedVersion} \u2192 ${currentVersion}`));
3809
4660
  const shouldUpdate = await promptYesNo("Update to latest version?");
3810
4661
  if (shouldUpdate) {
3811
- console.log(chalk10.dim("\n Updating @colbymchenry/cmem...\n"));
4662
+ console.log(chalk9.dim("\n Updating @colbymchenry/cmem...\n"));
3812
4663
  try {
3813
- execSync3("npm install -g @colbymchenry/cmem", { stdio: "inherit" });
3814
- console.log(chalk10.green("\n \u2713 Updated\n"));
4664
+ execSync2("npm install -g @colbymchenry/cmem", { stdio: "inherit" });
4665
+ console.log(chalk9.green("\n \u2713 Updated\n"));
3815
4666
  } catch {
3816
- console.log(chalk10.red("\n \u2717 Failed to update\n"));
4667
+ console.log(chalk9.red("\n \u2717 Failed to update\n"));
3817
4668
  }
3818
4669
  } else {
3819
4670
  console.log("");
3820
4671
  }
3821
4672
  } else {
3822
- console.log(chalk10.green(" \u2713 cmem is installed globally\n"));
4673
+ console.log(chalk9.green(" \u2713 cmem is installed globally\n"));
3823
4674
  }
3824
- console.log(chalk10.yellow(" Semantic search setup"));
3825
- console.log(chalk10.dim(" Enables searching conversations by meaning\n"));
4675
+ console.log(chalk9.yellow(" Semantic search setup"));
4676
+ console.log(chalk9.dim(" Enables searching conversations by meaning\n"));
3826
4677
  const modelSpinner = new Spinner3("Checking embedding model...");
3827
4678
  modelSpinner.start();
3828
4679
  const cached = isModelCached();
@@ -3843,102 +4694,88 @@ async function setupCommand() {
3843
4694
  modelSpinner.succeed("Embedding model ready");
3844
4695
  } catch (err) {
3845
4696
  modelSpinner.fail("Failed to download embedding model");
3846
- console.log(chalk10.dim(` Error: ${err}
4697
+ console.log(chalk9.dim(` Error: ${err}
3847
4698
  `));
3848
4699
  }
3849
4700
  }
3850
4701
  console.log("");
3851
- const daemonInstalled = existsSync11(
3852
- join7(homedir3(), "Library", "LaunchAgents", "com.cmem.watch.plist")
3853
- );
3854
- const isUpdating = installedVersion && installedVersion !== currentVersion;
3855
- if (daemonInstalled && !isUpdating) {
3856
- console.log(chalk10.green(" \u2713 Auto-start daemon is installed"));
3857
- try {
3858
- execSync3("launchctl load ~/Library/LaunchAgents/com.cmem.watch.plist 2>/dev/null", { stdio: "ignore" });
3859
- } catch {
3860
- }
3861
- console.log("");
3862
- } else if (daemonInstalled && isUpdating && process.platform === "darwin") {
3863
- console.log(chalk10.yellow(" Updating daemon..."));
3864
- try {
3865
- const { installCommand: installCommand2 } = await Promise.resolve().then(() => (init_install(), install_exports));
3866
- await installCommand2();
3867
- } catch {
3868
- console.log(chalk10.red(" \u2717 Failed to update daemon"));
3869
- console.log(chalk10.dim(" Try manually: cmem install\n"));
3870
- }
3871
- } else if (process.platform === "darwin") {
3872
- console.log(chalk10.yellow(" Auto-start daemon?"));
3873
- console.log(chalk10.dim(" Syncs Claude Code sessions in the background\n"));
4702
+ const mcpConfigured = isMcpConfigured();
4703
+ if (mcpConfigured) {
4704
+ console.log(chalk9.green(" \u2713 MCP server configured in Claude Code\n"));
4705
+ } else {
4706
+ console.log(chalk9.yellow(" Add MCP server to Claude Code?"));
4707
+ console.log(chalk9.dim(" Lets Claude search your past conversations\n"));
3874
4708
  const choice = await promptChoice("", [
3875
- "Yes - start on login (recommended)",
3876
- "No - I'll run `cmem watch` manually"
4709
+ "Yes - configure automatically (recommended)",
4710
+ "No - I'll configure it manually"
3877
4711
  ], 1);
3878
4712
  if (choice === 1) {
3879
- try {
3880
- const { installCommand: installCommand2 } = await Promise.resolve().then(() => (init_install(), install_exports));
3881
- await installCommand2();
3882
- } catch (err) {
3883
- console.log(chalk10.red(" \u2717 Failed to install daemon"));
3884
- console.log(chalk10.dim(" Try manually: cmem install\n"));
4713
+ const success = configureMcpServer();
4714
+ if (success) {
4715
+ console.log(chalk9.green("\n \u2713 Added MCP server to ~/.claude.json"));
4716
+ console.log(chalk9.green(" \u2713 Added permissions to ~/.claude/settings.json"));
4717
+ console.log(chalk9.dim(" Restart Claude Code or run /mcp to connect\n"));
4718
+ } else {
4719
+ console.log(chalk9.red("\n \u2717 Failed to configure MCP server\n"));
3885
4720
  }
3886
4721
  } else {
3887
- console.log(chalk10.dim("\n Skipped. Run `cmem watch` when needed.\n"));
4722
+ console.log(chalk9.dim("\n Skipped.\n"));
3888
4723
  }
3889
4724
  }
3890
- const mcpConfigured = isMcpConfigured();
3891
- if (mcpConfigured) {
3892
- console.log(chalk10.green(" \u2713 MCP server configured in Claude Code\n"));
4725
+ const hooksConfigured = areHooksConfigured();
4726
+ if (hooksConfigured) {
4727
+ console.log(chalk9.green(" \u2713 Learning hooks configured\n"));
3893
4728
  } else {
3894
- console.log(chalk10.yellow(" Add MCP server to Claude Code?"));
3895
- console.log(chalk10.dim(" Lets Claude search your past conversations\n"));
4729
+ console.log(chalk9.yellow(" Enable automatic learning?"));
4730
+ console.log(chalk9.dim(" Hooks sync sessions and inject knowledge automatically\n"));
3896
4731
  const choice = await promptChoice("", [
3897
- "Yes - configure automatically (recommended)",
3898
- "No - I'll configure it manually"
4732
+ "Yes - enable hooks (recommended)",
4733
+ "No - I'll manage manually"
3899
4734
  ], 1);
3900
4735
  if (choice === 1) {
3901
- const success = configureMcpServer();
3902
- if (success) {
3903
- console.log(chalk10.green("\n \u2713 Added MCP server to ~/.claude.json"));
3904
- console.log(chalk10.green(" \u2713 Added permissions to ~/.claude/settings.json"));
3905
- console.log(chalk10.dim(" Restart Claude Code or run /mcp to connect\n"));
4736
+ const result = configureHooks();
4737
+ if (result.success) {
4738
+ console.log(chalk9.green("\n \u2713 Configured hooks in ~/.claude/settings.json"));
4739
+ console.log(chalk9.dim(" \u2022 UserPromptSubmit: Injects relevant lessons"));
4740
+ console.log(chalk9.dim(" \u2022 Stop/PreCompact: Syncs and backs up sessions\n"));
3906
4741
  } else {
3907
- console.log(chalk10.red("\n \u2717 Failed to configure MCP server\n"));
4742
+ console.log(chalk9.red(`
4743
+ \u2717 Failed to configure hooks: ${result.message}`));
4744
+ console.log(chalk9.dim(" You can configure hooks manually in ~/.claude/settings.json\n"));
3908
4745
  }
3909
4746
  } else {
3910
- console.log(chalk10.dim("\n Skipped.\n"));
4747
+ console.log(chalk9.dim("\n Skipped. Run `cmem watch` for manual sync.\n"));
3911
4748
  }
3912
4749
  }
3913
- console.log(chalk10.yellow(" Initial session indexing"));
3914
- console.log(chalk10.dim(" Scanning and indexing your existing Claude Code conversations\n"));
4750
+ console.log(chalk9.yellow(" Initial session indexing"));
4751
+ console.log(chalk9.dim(" Scanning and indexing your existing Claude Code conversations\n"));
3915
4752
  await indexExistingSessions();
3916
- if (!existsSync11(CMEM_DIR2)) {
3917
- mkdirSync3(CMEM_DIR2, { recursive: true });
3918
- }
3919
- writeFileSync2(SETUP_MARKER, (/* @__PURE__ */ new Date()).toISOString());
3920
- console.log(chalk10.magenta(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
3921
- console.log(chalk10.green.bold("\n \u2713 Setup complete!\n"));
3922
- console.log(chalk10.white(" Commands:"));
3923
- console.log(chalk10.dim(" cmem Browse sessions (TUI)"));
3924
- console.log(chalk10.dim(' cmem search "X" Semantic search'));
3925
- console.log(chalk10.dim(" cmem watch Start sync daemon"));
3926
- console.log(chalk10.dim(" cmem stats Storage statistics"));
3927
- console.log(chalk10.dim(" cmem --help All commands"));
3928
- console.log(chalk10.white("\n MCP Tools (available to Claude):"));
3929
- console.log(chalk10.dim(" search_sessions Find past conversations"));
3930
- console.log(chalk10.dim(" get_session Retrieve full history"));
3931
- console.log(chalk10.dim(" list_sessions Browse recent sessions"));
3932
- if (process.platform === "darwin") {
3933
- const plistExists = existsSync11(join7(homedir3(), "Library", "LaunchAgents", "com.cmem.watch.plist"));
3934
- if (plistExists) {
3935
- console.log(chalk10.green("\n \u{1F680} Daemon is now syncing your sessions in the background!"));
3936
- }
4753
+ if (!existsSync10(CMEM_DIR2)) {
4754
+ mkdirSync2(CMEM_DIR2, { recursive: true });
4755
+ }
4756
+ writeFileSync(SETUP_MARKER, (/* @__PURE__ */ new Date()).toISOString());
4757
+ console.log(chalk9.magenta(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
4758
+ console.log(chalk9.green.bold("\n \u2713 Setup complete!\n"));
4759
+ console.log(chalk9.white(" Commands:"));
4760
+ console.log(chalk9.dim(" cmem Browse sessions (TUI)"));
4761
+ console.log(chalk9.dim(' cmem search "X" Semantic search'));
4762
+ console.log(chalk9.dim(" cmem synthesize Extract lessons from sessions"));
4763
+ console.log(chalk9.dim(" cmem stats Storage statistics"));
4764
+ console.log(chalk9.dim(" cmem --help All commands"));
4765
+ console.log(chalk9.white("\n MCP Tools (available to Claude):"));
4766
+ console.log(chalk9.dim(" search_sessions Find past conversations"));
4767
+ console.log(chalk9.dim(" search_lessons Find project knowledge"));
4768
+ console.log(chalk9.dim(" save_lesson Save important insights"));
4769
+ if (areHooksConfigured()) {
4770
+ console.log(chalk9.green("\n \u{1F9E0} Learning is enabled!"));
4771
+ console.log(chalk9.dim(" \u2022 Lessons are injected before each prompt"));
4772
+ console.log(chalk9.dim(" \u2022 Sessions sync automatically on stop/compact"));
4773
+ console.log(chalk9.dim(" \u2022 Run `cmem synthesize` to extract lessons"));
3937
4774
  }
3938
4775
  console.log("");
3939
4776
  }
3940
4777
  function isMcpConfigured() {
3941
- if (!existsSync11(CLAUDE_JSON_PATH)) {
4778
+ if (!existsSync10(CLAUDE_JSON_PATH)) {
3942
4779
  return false;
3943
4780
  }
3944
4781
  try {
@@ -3951,7 +4788,7 @@ function isMcpConfigured() {
3951
4788
  function configureMcpServer() {
3952
4789
  try {
3953
4790
  let claudeJson = {};
3954
- if (existsSync11(CLAUDE_JSON_PATH)) {
4791
+ if (existsSync10(CLAUDE_JSON_PATH)) {
3955
4792
  try {
3956
4793
  claudeJson = JSON.parse(readFileSync3(CLAUDE_JSON_PATH, "utf-8"));
3957
4794
  } catch {
@@ -3966,13 +4803,13 @@ function configureMcpServer() {
3966
4803
  command: "cmem",
3967
4804
  args: ["mcp"]
3968
4805
  };
3969
- writeFileSync2(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2) + "\n");
3970
- const claudeDir = dirname7(CLAUDE_SETTINGS_PATH);
3971
- if (!existsSync11(claudeDir)) {
3972
- mkdirSync3(claudeDir, { recursive: true });
4806
+ writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2) + "\n");
4807
+ const claudeDir = dirname6(CLAUDE_SETTINGS_PATH);
4808
+ if (!existsSync10(claudeDir)) {
4809
+ mkdirSync2(claudeDir, { recursive: true });
3973
4810
  }
3974
4811
  let settings = {};
3975
- if (existsSync11(CLAUDE_SETTINGS_PATH)) {
4812
+ if (existsSync10(CLAUDE_SETTINGS_PATH)) {
3976
4813
  try {
3977
4814
  settings = JSON.parse(readFileSync3(CLAUDE_SETTINGS_PATH, "utf-8"));
3978
4815
  } catch {
@@ -3990,16 +4827,106 @@ function configureMcpServer() {
3990
4827
  settings.permissions.allow.push(perm);
3991
4828
  }
3992
4829
  }
3993
- writeFileSync2(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
4830
+ writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
3994
4831
  return true;
3995
4832
  } catch (err) {
3996
- console.error(chalk10.red("Error configuring MCP:"), err);
4833
+ console.error(chalk9.red("Error configuring MCP:"), err);
4834
+ return false;
4835
+ }
4836
+ }
4837
+ function configureHooks() {
4838
+ try {
4839
+ const distPath = getCmemDistPath();
4840
+ if (!distPath) {
4841
+ return { success: false, message: "Could not find cmem installation path" };
4842
+ }
4843
+ const consultPath = join6(distPath, "hooks", "consult.js");
4844
+ const syncPath = join6(distPath, "hooks", "sync.js");
4845
+ if (!existsSync10(consultPath)) {
4846
+ return { success: false, message: `Hook file not found: ${consultPath}` };
4847
+ }
4848
+ const claudeDir = dirname6(CLAUDE_SETTINGS_PATH);
4849
+ if (!existsSync10(claudeDir)) {
4850
+ mkdirSync2(claudeDir, { recursive: true });
4851
+ }
4852
+ let settings = {};
4853
+ if (existsSync10(CLAUDE_SETTINGS_PATH)) {
4854
+ try {
4855
+ settings = JSON.parse(readFileSync3(CLAUDE_SETTINGS_PATH, "utf-8"));
4856
+ } catch {
4857
+ settings = {};
4858
+ }
4859
+ }
4860
+ if (!settings.hooks) {
4861
+ settings.hooks = {};
4862
+ }
4863
+ const hasCmemHook = (hookArray) => {
4864
+ if (!hookArray) return false;
4865
+ return hookArray.some((h) => h.hooks?.some((hk) => hk.command?.includes("cmem")));
4866
+ };
4867
+ if (!hasCmemHook(settings.hooks.UserPromptSubmit)) {
4868
+ if (!settings.hooks.UserPromptSubmit) {
4869
+ settings.hooks.UserPromptSubmit = [];
4870
+ }
4871
+ settings.hooks.UserPromptSubmit.push({
4872
+ matcher: ".*",
4873
+ hooks: [{
4874
+ type: "command",
4875
+ command: `node "${consultPath}"`,
4876
+ timeout: 5e3
4877
+ }]
4878
+ });
4879
+ }
4880
+ if (!hasCmemHook(settings.hooks.Stop)) {
4881
+ if (!settings.hooks.Stop) {
4882
+ settings.hooks.Stop = [];
4883
+ }
4884
+ settings.hooks.Stop.push({
4885
+ matcher: ".*",
4886
+ hooks: [{
4887
+ type: "command",
4888
+ command: `node "${syncPath}"`
4889
+ }]
4890
+ });
4891
+ }
4892
+ if (!hasCmemHook(settings.hooks.PreCompact)) {
4893
+ if (!settings.hooks.PreCompact) {
4894
+ settings.hooks.PreCompact = [];
4895
+ }
4896
+ settings.hooks.PreCompact.push({
4897
+ matcher: ".*",
4898
+ hooks: [{
4899
+ type: "command",
4900
+ command: `node "${syncPath}"`
4901
+ }]
4902
+ });
4903
+ }
4904
+ writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
4905
+ return { success: true, message: "Hooks configured successfully" };
4906
+ } catch (err) {
4907
+ return { success: false, message: String(err) };
4908
+ }
4909
+ }
4910
+ function areHooksConfigured() {
4911
+ if (!existsSync10(CLAUDE_SETTINGS_PATH)) {
4912
+ return false;
4913
+ }
4914
+ try {
4915
+ const settings = JSON.parse(readFileSync3(CLAUDE_SETTINGS_PATH, "utf-8"));
4916
+ const hooks = settings.hooks;
4917
+ if (!hooks) return false;
4918
+ const hasCmemHook = (hookArray) => {
4919
+ if (!hookArray) return false;
4920
+ return hookArray.some((h) => h.hooks?.some((hk) => hk.command?.includes("cmem")));
4921
+ };
4922
+ return hasCmemHook(hooks.UserPromptSubmit) || hasCmemHook(hooks.Stop);
4923
+ } catch {
3997
4924
  return false;
3998
4925
  }
3999
4926
  }
4000
4927
  function shouldRunSetup() {
4001
4928
  const isNpx = isRunningViaNpx();
4002
- const setupComplete = existsSync11(SETUP_MARKER);
4929
+ const setupComplete = existsSync10(SETUP_MARKER);
4003
4930
  const isGlobal = isGloballyInstalled();
4004
4931
  if (isNpx) return true;
4005
4932
  if (!isGlobal && !setupComplete) return true;
@@ -4012,13 +4939,13 @@ function shouldRunSetup() {
4012
4939
  }
4013
4940
  function findAllSessionFiles2(dir) {
4014
4941
  const files = [];
4015
- if (!existsSync11(dir)) return files;
4942
+ if (!existsSync10(dir)) return files;
4016
4943
  function scanDir(currentDir, depth = 0) {
4017
4944
  if (depth > 10) return;
4018
4945
  try {
4019
4946
  const entries = readdirSync4(currentDir, { withFileTypes: true });
4020
4947
  for (const entry of entries) {
4021
- const fullPath = join7(currentDir, entry.name);
4948
+ const fullPath = join6(currentDir, entry.name);
4022
4949
  if (entry.isDirectory()) {
4023
4950
  if (entry.name === "subagents") continue;
4024
4951
  scanDir(fullPath, depth + 1);
@@ -4078,7 +5005,7 @@ async function indexExistingSessions() {
4078
5005
  const totalFiles = sessionFiles.length;
4079
5006
  if (totalFiles === 0) {
4080
5007
  spinner.succeed("No Claude Code sessions found yet");
4081
- console.log(chalk10.dim(" Start using Claude Code, then run cmem to see your sessions\n"));
5008
+ console.log(chalk9.dim(" Start using Claude Code, then run cmem to see your sessions\n"));
4082
5009
  return;
4083
5010
  }
4084
5011
  const statsBefore = getStats();
@@ -4101,60 +5028,60 @@ async function indexExistingSessions() {
4101
5028
  }
4102
5029
  spinner.succeed(`Indexed ${totalFiles} sessions (${newlyIndexed} new)`);
4103
5030
  const statsAfter = getStats();
4104
- console.log(chalk10.dim(` ${statsAfter.sessionCount} sessions, ${statsAfter.embeddingCount} with embeddings
5031
+ console.log(chalk9.dim(` ${statsAfter.sessionCount} sessions, ${statsAfter.embeddingCount} with embeddings
4105
5032
  `));
4106
5033
  }
4107
5034
 
4108
5035
  // src/commands/purge.ts
4109
- import chalk11 from "chalk";
5036
+ import chalk10 from "chalk";
4110
5037
  import { createInterface as createInterface2 } from "readline";
4111
5038
  async function purgeCommand(options) {
4112
5039
  const days = options.days ? parseInt(options.days, 10) : DEFAULT_PURGE_DAYS;
4113
5040
  if (isNaN(days) || days < 1) {
4114
- console.log(chalk11.red("Invalid days value. Must be a positive number."));
5041
+ console.log(chalk10.red("Invalid days value. Must be a positive number."));
4115
5042
  return;
4116
5043
  }
4117
5044
  const preview = getPurgePreview(days);
4118
5045
  if (preview.sessionsToDelete === 0) {
4119
- console.log(chalk11.green("No sessions eligible for purge."));
4120
- console.log(chalk11.dim("(Sessions must be older than " + days + " days and not starred)"));
5046
+ console.log(chalk10.green("No sessions eligible for purge."));
5047
+ console.log(chalk10.dim("(Sessions must be older than " + days + " days and not starred)"));
4121
5048
  return;
4122
5049
  }
4123
- console.log(chalk11.bold("\nPurge Preview:"));
4124
- console.log(chalk11.dim("\u2500".repeat(50)));
4125
- console.log(` Database size: ${chalk11.cyan(formatBytes(getDatabaseSize()))}`);
4126
- console.log(` Backups size: ${chalk11.cyan(formatBytes(getBackupsDirSize()))}`);
4127
- console.log(` Total storage: ${chalk11.cyan(formatBytes(getTotalStorageSize()))}`);
5050
+ console.log(chalk10.bold("\nPurge Preview:"));
5051
+ console.log(chalk10.dim("\u2500".repeat(50)));
5052
+ console.log(` Database size: ${chalk10.cyan(formatBytes(getDatabaseSize()))}`);
5053
+ console.log(` Backups size: ${chalk10.cyan(formatBytes(getBackupsDirSize()))}`);
5054
+ console.log(` Total storage: ${chalk10.cyan(formatBytes(getTotalStorageSize()))}`);
4128
5055
  console.log();
4129
- console.log(` Sessions to delete: ${chalk11.yellow(preview.sessionsToDelete.toString())}`);
4130
- console.log(` Messages involved: ${chalk11.yellow(preview.messagesInvolved.toString())}`);
5056
+ console.log(` Sessions to delete: ${chalk10.yellow(preview.sessionsToDelete.toString())}`);
5057
+ console.log(` Messages involved: ${chalk10.yellow(preview.messagesInvolved.toString())}`);
4131
5058
  if (preview.backupFilesToDelete > 0) {
4132
- console.log(` Backup files: ${chalk11.yellow(preview.backupFilesToDelete.toString())} (~${formatBytes(preview.backupBytesToFree)})`);
5059
+ console.log(` Backup files: ${chalk10.yellow(preview.backupFilesToDelete.toString())} (~${formatBytes(preview.backupBytesToFree)})`);
4133
5060
  }
4134
- console.log(` Date range: ${chalk11.dim(preview.oldestSessionDate)} to ${chalk11.dim(preview.newestSessionDate)}`);
4135
- console.log(chalk11.dim(" (Starred sessions will be preserved)"));
4136
- console.log(chalk11.dim("\u2500".repeat(50)));
5061
+ console.log(` Date range: ${chalk10.dim(preview.oldestSessionDate)} to ${chalk10.dim(preview.newestSessionDate)}`);
5062
+ console.log(chalk10.dim(" (Starred sessions will be preserved)"));
5063
+ console.log(chalk10.dim("\u2500".repeat(50)));
4137
5064
  if (options.dryRun) {
4138
- console.log(chalk11.cyan("\n[Dry run] No changes made."));
5065
+ console.log(chalk10.cyan("\n[Dry run] No changes made."));
4139
5066
  return;
4140
5067
  }
4141
5068
  if (!options.force) {
4142
5069
  const confirmed = await confirm(
4143
- chalk11.red(`
5070
+ chalk10.red(`
4144
5071
  Delete ${preview.sessionsToDelete} sessions? This cannot be undone. [y/N] `)
4145
5072
  );
4146
5073
  if (!confirmed) {
4147
- console.log(chalk11.dim("Cancelled."));
5074
+ console.log(chalk10.dim("Cancelled."));
4148
5075
  return;
4149
5076
  }
4150
5077
  }
4151
5078
  const result = purgeOldSessions(days);
4152
- console.log(chalk11.green(`
5079
+ console.log(chalk10.green(`
4153
5080
  Purged ${result.sessionsDeleted} session${result.sessionsDeleted !== 1 ? "s" : ""}.`));
4154
5081
  if (result.backupsDeleted > 0) {
4155
- console.log(chalk11.green(`Deleted ${result.backupsDeleted} backup file${result.backupsDeleted !== 1 ? "s" : ""}.`));
5082
+ console.log(chalk10.green(`Deleted ${result.backupsDeleted} backup file${result.backupsDeleted !== 1 ? "s" : ""}.`));
4156
5083
  }
4157
- console.log(`New storage size: ${chalk11.cyan(formatBytes(getTotalStorageSize()))}`);
5084
+ console.log(`New storage size: ${chalk10.cyan(formatBytes(getTotalStorageSize()))}`);
4158
5085
  }
4159
5086
  function confirm(prompt) {
4160
5087
  const rl = createInterface2({
@@ -4169,13 +5096,582 @@ function confirm(prompt) {
4169
5096
  });
4170
5097
  }
4171
5098
 
5099
+ // src/commands/synthesize.ts
5100
+ import chalk11 from "chalk";
5101
+
5102
+ // src/learning/SynthesisEngine.ts
5103
+ var SIMILARITY_THRESHOLD = 0.85;
5104
+ var MAX_MESSAGES_FOR_SYNTHESIS = 50;
5105
+ var MAX_MESSAGE_LENGTH = 2e3;
5106
+ var SynthesisEngine = class {
5107
+ /**
5108
+ * Synthesize lessons from a session
5109
+ */
5110
+ async synthesize(session, messages) {
5111
+ const result = {
5112
+ lessonsCreated: 0,
5113
+ lessonsSkipped: 0,
5114
+ errors: []
5115
+ };
5116
+ if (!isClaudeCliAvailable()) {
5117
+ result.errors.push("Claude CLI not available for synthesis");
5118
+ return result;
5119
+ }
5120
+ if (messages.length < 3) {
5121
+ result.errors.push("Session too short for meaningful synthesis");
5122
+ return result;
5123
+ }
5124
+ try {
5125
+ const prompt = this.buildSynthesisPrompt(session, messages);
5126
+ const response = await runClaudePrompt(prompt, {
5127
+ model: "haiku",
5128
+ // Use haiku for speed and cost
5129
+ maxTokens: 2e3
5130
+ });
5131
+ if (!response.success) {
5132
+ result.errors.push(`Claude error: ${response.error}`);
5133
+ return result;
5134
+ }
5135
+ const rawLessons = this.parseResponse(response.content);
5136
+ if (rawLessons.length === 0) {
5137
+ return result;
5138
+ }
5139
+ const projectPath = session.projectPath || "";
5140
+ for (const raw of rawLessons) {
5141
+ try {
5142
+ const stored = await this.dedupeAndStore(raw, session, projectPath);
5143
+ if (stored) {
5144
+ result.lessonsCreated++;
5145
+ } else {
5146
+ result.lessonsSkipped++;
5147
+ }
5148
+ } catch (error) {
5149
+ result.errors.push(`Failed to store lesson: ${String(error)}`);
5150
+ }
5151
+ }
5152
+ return result;
5153
+ } catch (error) {
5154
+ result.errors.push(`Synthesis failed: ${String(error)}`);
5155
+ return result;
5156
+ }
5157
+ }
5158
+ /**
5159
+ * Build the synthesis prompt for Claude
5160
+ */
5161
+ buildSynthesisPrompt(session, messages) {
5162
+ const formattedMessages = this.formatMessages(messages);
5163
+ return `You are analyzing a Claude Code conversation session to extract reusable lessons.
5164
+
5165
+ ## Session Information
5166
+ - Project: ${session.projectPath || "Unknown"}
5167
+ - Title: ${session.title}
5168
+ - Messages: ${messages.length}
5169
+
5170
+ ## Conversation
5171
+ ${formattedMessages}
5172
+
5173
+ ## Task
5174
+ Extract reusable lessons from this conversation. Focus on:
5175
+
5176
+ 1. **Architecture Decisions** - Design choices and their rationale
5177
+ 2. **Anti-Patterns** - What NOT to do and why
5178
+ 3. **Bug Patterns** - Common bugs and their root causes
5179
+ 4. **Project Conventions** - Code style, naming, file organization
5180
+ 5. **Dependency Knowledge** - Library quirks, version issues, APIs
5181
+ 6. **Domain Knowledge** - Business logic, rules, requirements
5182
+ 7. **Workflows** - Processes, commands, deployment steps
5183
+
5184
+ ## Requirements
5185
+ - Only extract genuinely reusable lessons
5186
+ - Be specific and actionable, not generic
5187
+ - Include context for when the lesson applies
5188
+ - Skip trivial or one-off fixes
5189
+ - Focus on knowledge that would help future work
5190
+
5191
+ ## Output Format
5192
+ Return a JSON array of lessons. Each lesson must have:
5193
+ \`\`\`json
5194
+ [
5195
+ {
5196
+ "category": "architecture_decision|anti_pattern|bug_pattern|project_convention|dependency_knowledge|domain_knowledge|workflow|other",
5197
+ "title": "Short descriptive title (max 60 chars)",
5198
+ "trigger_context": "When this lesson should be surfaced (what kind of prompt/task)",
5199
+ "insight": "The actual knowledge - specific and actionable",
5200
+ "reasoning": "Why this is true or important (optional)",
5201
+ "confidence": 0.3-0.7
5202
+ }
5203
+ ]
5204
+ \`\`\`
5205
+
5206
+ Confidence guidelines:
5207
+ - 0.3-0.4: Observed once, might be specific to this case
5208
+ - 0.5: Reasonable general lesson
5209
+ - 0.6-0.7: Clear pattern with strong evidence
5210
+
5211
+ Return an empty array [] if no reusable lessons can be extracted.
5212
+
5213
+ Output only the JSON array, no other text.`;
5214
+ }
5215
+ /**
5216
+ * Format messages for the prompt
5217
+ */
5218
+ formatMessages(messages) {
5219
+ const relevantMessages = messages.slice(-MAX_MESSAGES_FOR_SYNTHESIS);
5220
+ return relevantMessages.map((m) => {
5221
+ const role = m.role === "user" ? "User" : "Assistant";
5222
+ const content = m.content.length > MAX_MESSAGE_LENGTH ? m.content.slice(0, MAX_MESSAGE_LENGTH) + "...[truncated]" : m.content;
5223
+ return `### ${role}
5224
+ ${content}`;
5225
+ }).join("\n\n");
5226
+ }
5227
+ /**
5228
+ * Parse Claude's response into raw lessons
5229
+ */
5230
+ parseResponse(content) {
5231
+ try {
5232
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
5233
+ if (!jsonMatch) {
5234
+ return [];
5235
+ }
5236
+ const parsed = JSON.parse(jsonMatch[0]);
5237
+ if (!Array.isArray(parsed)) {
5238
+ return [];
5239
+ }
5240
+ return parsed.filter((item) => this.isValidRawLesson(item)).map((item) => ({
5241
+ category: item.category,
5242
+ title: String(item.title).slice(0, 60),
5243
+ triggerContext: String(item.trigger_context),
5244
+ insight: String(item.insight),
5245
+ reasoning: item.reasoning ? String(item.reasoning) : void 0,
5246
+ confidence: Math.min(0.7, Math.max(0.3, Number(item.confidence) || 0.5))
5247
+ }));
5248
+ } catch {
5249
+ return [];
5250
+ }
5251
+ }
5252
+ /**
5253
+ * Validate a raw lesson object
5254
+ */
5255
+ isValidRawLesson(item) {
5256
+ if (typeof item !== "object" || item === null) {
5257
+ return false;
5258
+ }
5259
+ const obj = item;
5260
+ return typeof obj.category === "string" && VALID_CATEGORIES.includes(obj.category) && typeof obj.title === "string" && obj.title.length > 0 && typeof obj.trigger_context === "string" && obj.trigger_context.length > 0 && typeof obj.insight === "string" && obj.insight.length > 0;
5261
+ }
5262
+ /**
5263
+ * Check for duplicates and store if unique
5264
+ */
5265
+ async dedupeAndStore(raw, session, projectPath) {
5266
+ const embeddingText = `${raw.title} ${raw.triggerContext} ${raw.insight}`;
5267
+ try {
5268
+ const embedding = await getEmbedding(embeddingText);
5269
+ const similar = searchLessonsByEmbedding(embedding, projectPath, 1);
5270
+ if (similar.length > 0 && this.isTooSimilar(similar[0], raw)) {
5271
+ return null;
5272
+ }
5273
+ const input = {
5274
+ projectPath,
5275
+ category: raw.category,
5276
+ title: raw.title,
5277
+ triggerContext: raw.triggerContext,
5278
+ insight: raw.insight,
5279
+ reasoning: raw.reasoning,
5280
+ confidence: raw.confidence,
5281
+ sourceSessionId: session.id,
5282
+ sourceType: "synthesized"
5283
+ };
5284
+ const lesson = await lessonManager.create(input);
5285
+ return lesson;
5286
+ } catch {
5287
+ const input = {
5288
+ projectPath,
5289
+ category: raw.category,
5290
+ title: raw.title,
5291
+ triggerContext: raw.triggerContext,
5292
+ insight: raw.insight,
5293
+ reasoning: raw.reasoning,
5294
+ confidence: raw.confidence,
5295
+ sourceSessionId: session.id,
5296
+ sourceType: "synthesized"
5297
+ };
5298
+ return lessonManager.create(input);
5299
+ }
5300
+ }
5301
+ /**
5302
+ * Check if a raw lesson is too similar to an existing lesson
5303
+ */
5304
+ isTooSimilar(existing, raw) {
5305
+ const existingText = `${existing.title} ${existing.insight}`.toLowerCase();
5306
+ const rawText = `${raw.title} ${raw.insight}`.toLowerCase();
5307
+ const existingWords = new Set(existingText.split(/\s+/));
5308
+ const rawWords = rawText.split(/\s+/);
5309
+ let commonCount = 0;
5310
+ for (const word of rawWords) {
5311
+ if (existingWords.has(word)) {
5312
+ commonCount++;
5313
+ }
5314
+ }
5315
+ const similarity = commonCount / Math.max(existingWords.size, rawWords.length);
5316
+ return similarity > SIMILARITY_THRESHOLD;
5317
+ }
5318
+ };
5319
+ var synthesisEngine = new SynthesisEngine();
5320
+
5321
+ // src/learning/processQueue.ts
5322
+ async function processSynthesisQueue(limit = 5) {
5323
+ const result = {
5324
+ processed: 0,
5325
+ lessonsCreated: 0,
5326
+ failed: 0,
5327
+ errors: []
5328
+ };
5329
+ const pending = getPendingSynthesis(limit);
5330
+ if (pending.length === 0) {
5331
+ return result;
5332
+ }
5333
+ for (const item of pending) {
5334
+ try {
5335
+ markSynthesisProcessing(item.id);
5336
+ const session = getSession(item.sessionId);
5337
+ if (!session) {
5338
+ markSynthesisFailed(item.id, "Session not found");
5339
+ result.failed++;
5340
+ result.errors.push(`Session not found: ${item.sessionId}`);
5341
+ continue;
5342
+ }
5343
+ const messages = getSessionMessages(session.id);
5344
+ if (messages.length === 0) {
5345
+ markSynthesisFailed(item.id, "Session has no messages");
5346
+ result.failed++;
5347
+ continue;
5348
+ }
5349
+ const synthesisResult = await synthesisEngine.synthesize(session, messages);
5350
+ if (synthesisResult.errors.length > 0) {
5351
+ result.errors.push(...synthesisResult.errors);
5352
+ }
5353
+ markSynthesisComplete(item.id, synthesisResult.lessonsCreated);
5354
+ result.processed++;
5355
+ result.lessonsCreated += synthesisResult.lessonsCreated;
5356
+ } catch (error) {
5357
+ markSynthesisFailed(item.id, String(error));
5358
+ result.failed++;
5359
+ result.errors.push(`Failed to process ${item.sessionId}: ${String(error)}`);
5360
+ }
5361
+ }
5362
+ return result;
5363
+ }
5364
+ function getQueueStatus2() {
5365
+ return getSynthesisStats();
5366
+ }
5367
+
5368
+ // src/commands/synthesize.ts
5369
+ async function synthesizeCommand(options) {
5370
+ const status = getQueueStatus2();
5371
+ const lessonStats = getLessonStats();
5372
+ if (options.status) {
5373
+ console.log(chalk11.cyan("Synthesis Queue Status\n"));
5374
+ console.log(` Pending: ${chalk11.yellow(status.pending)}`);
5375
+ console.log(` Processing: ${chalk11.blue(status.processing)}`);
5376
+ console.log(` Completed: ${chalk11.green(status.completed)}`);
5377
+ console.log(` Failed: ${chalk11.red(status.failed)}`);
5378
+ console.log("");
5379
+ console.log(chalk11.cyan("Lesson Statistics\n"));
5380
+ console.log(` Total: ${lessonStats.totalLessons}`);
5381
+ console.log(` Active: ${lessonStats.activeLessons}`);
5382
+ console.log(` Archived: ${lessonStats.archivedLessons}`);
5383
+ console.log(` Avg Confidence: ${(lessonStats.avgConfidence * 100).toFixed(0)}%`);
5384
+ console.log("");
5385
+ console.log(chalk11.dim("Categories:"));
5386
+ for (const [category, count] of Object.entries(lessonStats.byCategory)) {
5387
+ if (count > 0) {
5388
+ console.log(` ${category}: ${count}`);
5389
+ }
5390
+ }
5391
+ return;
5392
+ }
5393
+ if (status.pending === 0) {
5394
+ console.log(chalk11.yellow("No sessions pending synthesis."));
5395
+ console.log(chalk11.dim(`
5396
+ Total lessons: ${lessonStats.totalLessons}`));
5397
+ return;
5398
+ }
5399
+ const limit = parseInt(options.limit || "5", 10);
5400
+ console.log(chalk11.cyan("Processing synthesis queue...\n"));
5401
+ console.log(` Queue: ${status.pending} pending`);
5402
+ console.log(` Processing up to ${limit} sessions
5403
+ `);
5404
+ const result = await processSynthesisQueue(limit);
5405
+ console.log(chalk11.cyan("\nResults:\n"));
5406
+ console.log(` Processed: ${chalk11.green(result.processed)}`);
5407
+ console.log(` Lessons created: ${chalk11.green(result.lessonsCreated)}`);
5408
+ console.log(` Failed: ${result.failed > 0 ? chalk11.red(result.failed) : "0"}`);
5409
+ if (result.errors.length > 0) {
5410
+ console.log(chalk11.yellow("\nWarnings:"));
5411
+ for (const error of result.errors.slice(0, 5)) {
5412
+ console.log(chalk11.dim(` - ${error}`));
5413
+ }
5414
+ if (result.errors.length > 5) {
5415
+ console.log(chalk11.dim(` ... and ${result.errors.length - 5} more`));
5416
+ }
5417
+ }
5418
+ const newStats = getLessonStats();
5419
+ const newStatus = getQueueStatus2();
5420
+ console.log("");
5421
+ console.log(`Remaining in queue: ${newStatus.pending}`);
5422
+ console.log(`Total lessons: ${newStats.totalLessons}`);
5423
+ try {
5424
+ const cleaned = cleanupOldInjections(7);
5425
+ if (cleaned > 0) {
5426
+ console.log(chalk11.dim(`
5427
+ Cleaned up ${cleaned} old injection cache records`));
5428
+ }
5429
+ } catch {
5430
+ }
5431
+ }
5432
+
5433
+ // src/commands/gui.ts
5434
+ import chalk12 from "chalk";
5435
+
5436
+ // src/api/server.ts
5437
+ import express from "express";
5438
+ import cors from "cors";
5439
+ import { join as join7, dirname as dirname7 } from "path";
5440
+ import { fileURLToPath as fileURLToPath2 } from "url";
5441
+ import { existsSync as existsSync11 } from "fs";
5442
+
5443
+ // src/api/routes.ts
5444
+ import { Router } from "express";
5445
+ var apiRouter = Router();
5446
+ apiRouter.get("/lessons", (req, res) => {
5447
+ try {
5448
+ const {
5449
+ projectPath,
5450
+ category,
5451
+ archived,
5452
+ minConfidence,
5453
+ limit
5454
+ } = req.query;
5455
+ let lessons;
5456
+ if (projectPath) {
5457
+ lessons = getLessonsByProject(projectPath, {
5458
+ category,
5459
+ archived: archived === "true",
5460
+ minConfidence: minConfidence ? parseFloat(minConfidence) : void 0,
5461
+ limit: limit ? parseInt(limit, 10) : void 0
5462
+ });
5463
+ } else {
5464
+ lessons = getAllLessons({
5465
+ category,
5466
+ archived: archived === "true",
5467
+ minConfidence: minConfidence ? parseFloat(minConfidence) : void 0,
5468
+ limit: limit ? parseInt(limit, 10) : void 0
5469
+ });
5470
+ }
5471
+ res.json(lessons);
5472
+ } catch (error) {
5473
+ console.error("Error fetching lessons:", error);
5474
+ res.status(500).json({ message: "Failed to fetch lessons" });
5475
+ }
5476
+ });
5477
+ apiRouter.get("/lessons/:id", (req, res) => {
5478
+ try {
5479
+ const lesson = getLesson(req.params.id);
5480
+ if (!lesson) {
5481
+ res.status(404).json({ message: "Lesson not found" });
5482
+ return;
5483
+ }
5484
+ res.json(lesson);
5485
+ } catch (error) {
5486
+ console.error("Error fetching lesson:", error);
5487
+ res.status(500).json({ message: "Failed to fetch lesson" });
5488
+ }
5489
+ });
5490
+ apiRouter.post("/lessons", (req, res) => {
5491
+ try {
5492
+ const input = req.body;
5493
+ if (!input.projectPath || !input.category || !input.title || !input.triggerContext || !input.insight) {
5494
+ res.status(400).json({ message: "Missing required fields" });
5495
+ return;
5496
+ }
5497
+ const lesson = createLesson(input);
5498
+ res.status(201).json(lesson);
5499
+ } catch (error) {
5500
+ console.error("Error creating lesson:", error);
5501
+ res.status(500).json({ message: "Failed to create lesson" });
5502
+ }
5503
+ });
5504
+ apiRouter.put("/lessons/:id", (req, res) => {
5505
+ try {
5506
+ const existing = getLesson(req.params.id);
5507
+ if (!existing) {
5508
+ res.status(404).json({ message: "Lesson not found" });
5509
+ return;
5510
+ }
5511
+ const updates = req.body;
5512
+ const lesson = updateLesson(req.params.id, updates);
5513
+ res.json(lesson);
5514
+ } catch (error) {
5515
+ console.error("Error updating lesson:", error);
5516
+ res.status(500).json({ message: "Failed to update lesson" });
5517
+ }
5518
+ });
5519
+ apiRouter.delete("/lessons/:id", (req, res) => {
5520
+ try {
5521
+ const existing = getLesson(req.params.id);
5522
+ if (!existing) {
5523
+ res.status(404).json({ message: "Lesson not found" });
5524
+ return;
5525
+ }
5526
+ deleteLesson(req.params.id);
5527
+ res.status(204).send();
5528
+ } catch (error) {
5529
+ console.error("Error deleting lesson:", error);
5530
+ res.status(500).json({ message: "Failed to delete lesson" });
5531
+ }
5532
+ });
5533
+ apiRouter.post("/lessons/:id/validate", (req, res) => {
5534
+ try {
5535
+ const existing = getLesson(req.params.id);
5536
+ if (!existing) {
5537
+ res.status(404).json({ message: "Lesson not found" });
5538
+ return;
5539
+ }
5540
+ const { comment } = req.body;
5541
+ recordLessonValidation(req.params.id, comment);
5542
+ const newConfidence = Math.min(1, existing.confidence + 0.1);
5543
+ const lesson = updateLesson(req.params.id, { confidence: newConfidence });
5544
+ res.json(lesson);
5545
+ } catch (error) {
5546
+ console.error("Error validating lesson:", error);
5547
+ res.status(500).json({ message: "Failed to validate lesson" });
5548
+ }
5549
+ });
5550
+ apiRouter.post("/lessons/:id/reject", (req, res) => {
5551
+ try {
5552
+ const existing = getLesson(req.params.id);
5553
+ if (!existing) {
5554
+ res.status(404).json({ message: "Lesson not found" });
5555
+ return;
5556
+ }
5557
+ const { comment } = req.body;
5558
+ recordLessonRejection(req.params.id, comment);
5559
+ const newConfidence = Math.max(0, existing.confidence - 0.2);
5560
+ if (newConfidence < 0.1) {
5561
+ archiveLesson(req.params.id);
5562
+ }
5563
+ const lesson = updateLesson(req.params.id, { confidence: newConfidence });
5564
+ res.json(lesson);
5565
+ } catch (error) {
5566
+ console.error("Error rejecting lesson:", error);
5567
+ res.status(500).json({ message: "Failed to reject lesson" });
5568
+ }
5569
+ });
5570
+ apiRouter.post("/lessons/:id/archive", (req, res) => {
5571
+ try {
5572
+ const existing = getLesson(req.params.id);
5573
+ if (!existing) {
5574
+ res.status(404).json({ message: "Lesson not found" });
5575
+ return;
5576
+ }
5577
+ archiveLesson(req.params.id);
5578
+ const lesson = getLesson(req.params.id);
5579
+ res.json(lesson);
5580
+ } catch (error) {
5581
+ console.error("Error archiving lesson:", error);
5582
+ res.status(500).json({ message: "Failed to archive lesson" });
5583
+ }
5584
+ });
5585
+ apiRouter.get("/stats", (_req, res) => {
5586
+ try {
5587
+ const lessonStats = getLessonStats();
5588
+ const queueStatus = getQueueStatus();
5589
+ res.json({
5590
+ ...lessonStats,
5591
+ synthesisQueue: queueStatus
5592
+ });
5593
+ } catch (error) {
5594
+ console.error("Error fetching stats:", error);
5595
+ res.status(500).json({ message: "Failed to fetch stats" });
5596
+ }
5597
+ });
5598
+ apiRouter.get("/projects", (_req, res) => {
5599
+ try {
5600
+ const projects = getDistinctProjects();
5601
+ res.json(projects);
5602
+ } catch (error) {
5603
+ console.error("Error fetching projects:", error);
5604
+ res.status(500).json({ message: "Failed to fetch projects" });
5605
+ }
5606
+ });
5607
+
5608
+ // src/api/server.ts
5609
+ var __filename2 = fileURLToPath2(import.meta.url);
5610
+ var __dirname2 = dirname7(__filename2);
5611
+ var DEFAULT_PORT = 3848;
5612
+ async function startServer(options = {}) {
5613
+ const port = options.port ?? DEFAULT_PORT;
5614
+ getDatabase();
5615
+ const app = express();
5616
+ app.use(cors());
5617
+ app.use(express.json());
5618
+ app.use("/api", (_req, res, next) => {
5619
+ res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
5620
+ res.set("Pragma", "no-cache");
5621
+ res.set("Expires", "0");
5622
+ next();
5623
+ });
5624
+ app.use("/api", apiRouter);
5625
+ const guiDistPath = join7(__dirname2, "../gui/dist");
5626
+ if (existsSync11(guiDistPath)) {
5627
+ app.use(express.static(guiDistPath));
5628
+ app.get("/{*path}", (_req, res) => {
5629
+ res.sendFile(join7(guiDistPath, "index.html"));
5630
+ });
5631
+ } else {
5632
+ app.get("/", (_req, res) => {
5633
+ res.json({
5634
+ message: "CMEM API Server",
5635
+ note: "GUI not built. Run: cd gui && npm run build",
5636
+ endpoints: {
5637
+ lessons: "/api/lessons",
5638
+ stats: "/api/stats",
5639
+ projects: "/api/projects"
5640
+ }
5641
+ });
5642
+ });
5643
+ }
5644
+ app.listen(port, () => {
5645
+ console.log(`CMEM GUI server running at http://localhost:${port}`);
5646
+ if (options.open) {
5647
+ const url = `http://localhost:${port}`;
5648
+ const openCommand = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
5649
+ import("child_process").then(({ exec }) => {
5650
+ exec(openCommand);
5651
+ });
5652
+ }
5653
+ });
5654
+ }
5655
+
5656
+ // src/commands/gui.ts
5657
+ async function guiCommand(options) {
5658
+ const port = options.port ? parseInt(options.port, 10) : 3848;
5659
+ console.log(chalk12.cyan("Starting CMEM GUI..."));
5660
+ console.log("");
5661
+ await startServer({
5662
+ port,
5663
+ open: options.open !== false
5664
+ // Default to opening browser
5665
+ });
5666
+ }
5667
+
4172
5668
  // src/cli.ts
4173
5669
  var sessionToResume = null;
4174
5670
  function getVersion() {
4175
5671
  try {
4176
- const __filename2 = fileURLToPath2(import.meta.url);
4177
- const __dirname2 = dirname8(__filename2);
4178
- const packagePath = join8(__dirname2, "..", "package.json");
5672
+ const __filename3 = fileURLToPath3(import.meta.url);
5673
+ const __dirname3 = dirname8(__filename3);
5674
+ const packagePath = join8(__dirname3, "..", "package.json");
4179
5675
  if (existsSync12(packagePath)) {
4180
5676
  const pkg = JSON.parse(readFileSync4(packagePath, "utf-8"));
4181
5677
  return pkg.version || "0.1.0";
@@ -4248,14 +5744,11 @@ program.command("watch").description("Watch for Claude Code session changes and
4248
5744
  program.command("mcp").description("Start MCP server for Claude Code integration").action(async () => {
4249
5745
  await mcpCommand();
4250
5746
  });
4251
- program.command("install").description("Install watch daemon to run at system startup (macOS)").action(async () => {
4252
- await installCommand();
4253
- });
4254
- program.command("uninstall").description("Remove watch daemon from system startup").action(async () => {
4255
- await uninstallCommand();
5747
+ program.command("synthesize").description("Extract lessons from sessions in the synthesis queue").option("-n, --limit <n>", "Maximum number of sessions to process", "5").option("-s, --status", "Show queue status only").action(async (options) => {
5748
+ await synthesizeCommand(options);
4256
5749
  });
4257
- program.command("status").description("Check watch daemon status").action(async () => {
4258
- await statusCommand();
5750
+ program.command("gui").description("Launch the web-based lesson management interface").option("-p, --port <port>", "Port to run server on", "3848").option("--no-open", "Do not open browser automatically").action(async (options) => {
5751
+ await guiCommand(options);
4259
5752
  });
4260
5753
  program.parse();
4261
5754
  //# sourceMappingURL=cli.js.map