@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/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +11 -0
- package/README.md +180 -98
- package/commands/cmem.md +8 -0
- package/dist/cli.js +1866 -373
- package/dist/cli.js.map +1 -1
- package/dist/hooks/consult.js +1002 -0
- package/dist/hooks/consult.js.map +1 -0
- package/dist/hooks/sync.js +804 -0
- package/dist/hooks/sync.js.map +1 -0
- package/dist/hooks/synthesize.js +1329 -0
- package/dist/hooks/synthesize.js.map +1 -0
- package/dist/mcp/server.js +1850 -0
- package/dist/mcp/server.js.map +1 -0
- package/hooks/hooks.json +38 -0
- package/package.json +14 -7
- package/skills/memory-search/SKILL.md +12 -0
- package/scripts/postinstall.js +0 -46
package/dist/cli.js
CHANGED
|
@@ -1,194 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
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
|
|
3634
|
-
import { execSync as
|
|
3635
|
-
import { existsSync as
|
|
3636
|
-
import { homedir as
|
|
3637
|
-
import { join as
|
|
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 =
|
|
3642
|
-
var CLAUDE_JSON_PATH =
|
|
3643
|
-
var CLAUDE_SETTINGS_PATH =
|
|
3644
|
-
var CMEM_DIR2 =
|
|
3645
|
-
var SETUP_MARKER =
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
4488
|
+
console.log(`\r ${chalk9.green("\u2713")} ${message || this.message} `);
|
|
3675
4489
|
}
|
|
3676
4490
|
fail(message) {
|
|
3677
4491
|
this.stop();
|
|
3678
|
-
console.log(`\r ${
|
|
4492
|
+
console.log(`\r ${chalk9.red("\u2717")} ${message || this.message} `);
|
|
3679
4493
|
}
|
|
3680
4494
|
warn(message) {
|
|
3681
4495
|
this.stop();
|
|
3682
|
-
console.log(`\r ${
|
|
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 =
|
|
3693
|
-
const cyan =
|
|
3694
|
-
const 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(
|
|
4528
|
+
console.log(chalk9.dim(` ${i + 1}) ${opt}`));
|
|
3715
4529
|
});
|
|
3716
4530
|
console.log("");
|
|
3717
4531
|
return new Promise((resolve) => {
|
|
3718
|
-
rl.question(
|
|
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(
|
|
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 =
|
|
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 =
|
|
3765
|
-
if (
|
|
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 =
|
|
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(
|
|
3791
|
-
console.log(
|
|
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(
|
|
4648
|
+
console.log(chalk9.dim("\n Installing @colbymchenry/cmem globally...\n"));
|
|
3798
4649
|
try {
|
|
3799
|
-
|
|
3800
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4662
|
+
console.log(chalk9.dim("\n Updating @colbymchenry/cmem...\n"));
|
|
3812
4663
|
try {
|
|
3813
|
-
|
|
3814
|
-
console.log(
|
|
4664
|
+
execSync2("npm install -g @colbymchenry/cmem", { stdio: "inherit" });
|
|
4665
|
+
console.log(chalk9.green("\n \u2713 Updated\n"));
|
|
3815
4666
|
} catch {
|
|
3816
|
-
console.log(
|
|
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(
|
|
4673
|
+
console.log(chalk9.green(" \u2713 cmem is installed globally\n"));
|
|
3823
4674
|
}
|
|
3824
|
-
console.log(
|
|
3825
|
-
console.log(
|
|
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(
|
|
4697
|
+
console.log(chalk9.dim(` Error: ${err}
|
|
3847
4698
|
`));
|
|
3848
4699
|
}
|
|
3849
4700
|
}
|
|
3850
4701
|
console.log("");
|
|
3851
|
-
const
|
|
3852
|
-
|
|
3853
|
-
);
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
console.log(
|
|
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 -
|
|
3876
|
-
"No - I'll
|
|
4709
|
+
"Yes - configure automatically (recommended)",
|
|
4710
|
+
"No - I'll configure it manually"
|
|
3877
4711
|
], 1);
|
|
3878
4712
|
if (choice === 1) {
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
console.log(
|
|
3884
|
-
|
|
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(
|
|
4722
|
+
console.log(chalk9.dim("\n Skipped.\n"));
|
|
3888
4723
|
}
|
|
3889
4724
|
}
|
|
3890
|
-
const
|
|
3891
|
-
if (
|
|
3892
|
-
console.log(
|
|
4725
|
+
const hooksConfigured = areHooksConfigured();
|
|
4726
|
+
if (hooksConfigured) {
|
|
4727
|
+
console.log(chalk9.green(" \u2713 Learning hooks configured\n"));
|
|
3893
4728
|
} else {
|
|
3894
|
-
console.log(
|
|
3895
|
-
console.log(
|
|
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 -
|
|
3898
|
-
"No - I'll
|
|
4732
|
+
"Yes - enable hooks (recommended)",
|
|
4733
|
+
"No - I'll manage manually"
|
|
3899
4734
|
], 1);
|
|
3900
4735
|
if (choice === 1) {
|
|
3901
|
-
const
|
|
3902
|
-
if (success) {
|
|
3903
|
-
console.log(
|
|
3904
|
-
console.log(
|
|
3905
|
-
console.log(
|
|
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(
|
|
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(
|
|
4747
|
+
console.log(chalk9.dim("\n Skipped. Run `cmem watch` for manual sync.\n"));
|
|
3911
4748
|
}
|
|
3912
4749
|
}
|
|
3913
|
-
console.log(
|
|
3914
|
-
console.log(
|
|
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 (!
|
|
3917
|
-
|
|
3918
|
-
}
|
|
3919
|
-
|
|
3920
|
-
console.log(
|
|
3921
|
-
console.log(
|
|
3922
|
-
console.log(
|
|
3923
|
-
console.log(
|
|
3924
|
-
console.log(
|
|
3925
|
-
console.log(
|
|
3926
|
-
console.log(
|
|
3927
|
-
console.log(
|
|
3928
|
-
console.log(
|
|
3929
|
-
console.log(
|
|
3930
|
-
console.log(
|
|
3931
|
-
console.log(
|
|
3932
|
-
if (
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
3970
|
-
const claudeDir =
|
|
3971
|
-
if (!
|
|
3972
|
-
|
|
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 (
|
|
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
|
-
|
|
4830
|
+
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
3994
4831
|
return true;
|
|
3995
4832
|
} catch (err) {
|
|
3996
|
-
console.error(
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
4120
|
-
console.log(
|
|
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(
|
|
4124
|
-
console.log(
|
|
4125
|
-
console.log(` Database size: ${
|
|
4126
|
-
console.log(` Backups size: ${
|
|
4127
|
-
console.log(` Total storage: ${
|
|
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: ${
|
|
4130
|
-
console.log(` Messages involved: ${
|
|
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: ${
|
|
5059
|
+
console.log(` Backup files: ${chalk10.yellow(preview.backupFilesToDelete.toString())} (~${formatBytes(preview.backupBytesToFree)})`);
|
|
4133
5060
|
}
|
|
4134
|
-
console.log(` Date range: ${
|
|
4135
|
-
console.log(
|
|
4136
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
5070
|
+
chalk10.red(`
|
|
4144
5071
|
Delete ${preview.sessionsToDelete} sessions? This cannot be undone. [y/N] `)
|
|
4145
5072
|
);
|
|
4146
5073
|
if (!confirmed) {
|
|
4147
|
-
console.log(
|
|
5074
|
+
console.log(chalk10.dim("Cancelled."));
|
|
4148
5075
|
return;
|
|
4149
5076
|
}
|
|
4150
5077
|
}
|
|
4151
5078
|
const result = purgeOldSessions(days);
|
|
4152
|
-
console.log(
|
|
5079
|
+
console.log(chalk10.green(`
|
|
4153
5080
|
Purged ${result.sessionsDeleted} session${result.sessionsDeleted !== 1 ? "s" : ""}.`));
|
|
4154
5081
|
if (result.backupsDeleted > 0) {
|
|
4155
|
-
console.log(
|
|
5082
|
+
console.log(chalk10.green(`Deleted ${result.backupsDeleted} backup file${result.backupsDeleted !== 1 ? "s" : ""}.`));
|
|
4156
5083
|
}
|
|
4157
|
-
console.log(`New storage size: ${
|
|
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
|
|
4177
|
-
const
|
|
4178
|
-
const packagePath = join8(
|
|
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("
|
|
4252
|
-
await
|
|
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("
|
|
4258
|
-
await
|
|
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
|