kodelyth-ecc 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Kodelyth ECC — Auto Chat Detection (Memory Auto-Recall)
4
+ //
5
+ // Triggered on UserPromptSubmit (every message the user sends).
6
+ // Reads the prompt, searches memory in real-time, and injects relevant
7
+ // matches as additional context BEFORE the AI sees the prompt.
8
+ //
9
+ // Behaviour:
10
+ // - Skips on prompts that are too short (< 12 chars) or trivial ("ok", "yes")
11
+ // - Skips on prompts that look like agent commands (`use foo`, `@bar`)
12
+ // - Skips when no memory exists yet
13
+ // - Suppresses repeats: never re-surfaces the same memory twice in a session
14
+ // (state file: ~/.kodelyth/memory/session-surfaced-<sessionId>.json)
15
+ // - Always exits 0 — never blocks the prompt because memory is unavailable
16
+ // =============================================================================
17
+
18
+ 'use strict';
19
+
20
+ const fs = require('fs');
21
+ const os = require('os');
22
+ const path = require('path');
23
+
24
+ const MIN_PROMPT_CHARS = 12;
25
+ const MIN_TOKENS = 2;
26
+ const MAX_RECALLED = 3;
27
+ const MIN_SCORE = 1.0; // Higher than passive inject — we want strong signal
28
+ const TRIVIAL_PROMPTS = new Set(['ok', 'yes', 'no', 'thanks', 'thx', 'sure', 'go', 'cool', 'k']);
29
+ const SKIP_PATTERN = /^\s*(use\s+|@|\/|invoke\s+)/i;
30
+
31
+ let payload = '';
32
+ process.stdin.setEncoding('utf8');
33
+ process.stdin.on('data', chunk => { payload += chunk; });
34
+ process.stdin.on('end', main);
35
+ setTimeout(() => { if (!process.stdin.readableEnded) main(); }, 200);
36
+
37
+ function main() {
38
+ try {
39
+ const data = payload ? safeJson(payload) : {};
40
+ const userPrompt = (data.prompt || data.user_prompt || data.text || '').trim();
41
+ const sessionId = data.session_id || 'unknown';
42
+ const projectRoot = data.cwd || process.cwd();
43
+
44
+ if (!shouldRecall(userPrompt)) return done({});
45
+
46
+ // Lazy require so the hook doesn't crash if memory module breaks
47
+ const { recallForProject } = require(path.join(__dirname, '..', '..', 'scripts', 'memory', 'store'));
48
+
49
+ const matches = recallForProject(projectRoot, userPrompt, {
50
+ limit: MAX_RECALLED * 2, // grab extra so we can filter out repeats
51
+ minScore: MIN_SCORE,
52
+ });
53
+ if (matches.length === 0) return done({});
54
+
55
+ // Filter out memories already surfaced this session
56
+ const surfacedFile = surfacedStatePath(sessionId);
57
+ const surfaced = loadSurfaced(surfacedFile);
58
+ const fresh = matches.filter(m => !surfaced.has(m.id)).slice(0, MAX_RECALLED);
59
+ if (fresh.length === 0) return done({});
60
+
61
+ // Mark the freshly surfaced memories so they don't re-appear this session
62
+ for (const m of fresh) surfaced.add(m.id);
63
+ saveSurfaced(surfacedFile, surfaced);
64
+
65
+ const block = formatBlock(fresh, userPrompt);
66
+ done({
67
+ additionalContext: block,
68
+ meta: {
69
+ source: 'kodelyth-memory:auto-recall',
70
+ recalledCount: fresh.length,
71
+ surfacedTotal: surfaced.size,
72
+ },
73
+ });
74
+ } catch (err) {
75
+ process.stderr.write(`kodelyth-memory auto-recall: ${err.message}\n`);
76
+ done({});
77
+ }
78
+ }
79
+
80
+ function shouldRecall(prompt) {
81
+ if (!prompt || prompt.length < MIN_PROMPT_CHARS) return false;
82
+ if (TRIVIAL_PROMPTS.has(prompt.toLowerCase().trim())) return false;
83
+ if (SKIP_PATTERN.test(prompt)) return false;
84
+ // Token gate — need at least N meaningful words
85
+ const meaningful = prompt
86
+ .toLowerCase()
87
+ .split(/\s+/)
88
+ .filter(w => w.length >= 4);
89
+ return meaningful.length >= MIN_TOKENS;
90
+ }
91
+
92
+ function formatBlock(memories, userPrompt) {
93
+ const lines = [];
94
+ lines.push('## Kodelyth Memory — relevant past solutions');
95
+ lines.push('');
96
+ lines.push(`Auto-detected from your message: "${userPrompt.slice(0, 100).replace(/\n/g, ' ')}${userPrompt.length > 100 ? '...' : ''}"`);
97
+ lines.push('');
98
+ for (const m of memories) {
99
+ lines.push(`- **${m.problem}**`);
100
+ if (m.approach) lines.push(` Approach: ${m.approach.split('\n')[0].slice(0, 240)}`);
101
+ if (m.gotchas?.length) lines.push(` Gotcha: ${m.gotchas[0].slice(0, 200)}`);
102
+ if (m.tags?.length) lines.push(` Tags: ${m.tags.slice(0, 5).join(', ')}`);
103
+ lines.push(` (memory id: ${m.id} · captured ${m.captured_at?.slice(0, 10)})`);
104
+ }
105
+ lines.push('');
106
+ lines.push('> Surface these to the user before answering only if they are genuinely relevant to the current task. If not, ignore silently — do not force-fit a memory.');
107
+ return lines.join('\n');
108
+ }
109
+
110
+ function surfacedStatePath(sessionId) {
111
+ const dir = process.env.KODELYTH_MEMORY_DIR
112
+ || path.join(os.homedir(), '.kodelyth', 'memory');
113
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
114
+ return path.join(dir, `session-surfaced-${sessionId}.json`);
115
+ }
116
+
117
+ function loadSurfaced(file) {
118
+ try {
119
+ if (!fs.existsSync(file)) return new Set();
120
+ return new Set(JSON.parse(fs.readFileSync(file, 'utf8')));
121
+ } catch {
122
+ return new Set();
123
+ }
124
+ }
125
+
126
+ function saveSurfaced(file, set) {
127
+ try {
128
+ fs.writeFileSync(file, JSON.stringify(Array.from(set)));
129
+ } catch {
130
+ /* non-fatal */
131
+ }
132
+ }
133
+
134
+ function safeJson(s) {
135
+ try { return JSON.parse(s); } catch { return {}; }
136
+ }
137
+
138
+ function done(obj) {
139
+ if (Object.keys(obj).length > 0) process.stdout.write(JSON.stringify(obj));
140
+ process.exit(0);
141
+ }
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Kodelyth ECC — Memory Capture Hook (Stop)
4
+ //
5
+ // Runs at the end of a Claude Code session. Locates the session JSONL,
6
+ // extracts memory candidates, writes them to a review queue at:
7
+ // ~/.kodelyth/memory/pending-review.jsonl
8
+ //
9
+ // Candidates are NEVER auto-stored. The user reviews via:
10
+ // /memory review-pending
11
+ // or:
12
+ // node scripts/memory/cli.js list-pending
13
+ // =============================================================================
14
+
15
+ 'use strict';
16
+
17
+ const fs = require('fs');
18
+ const os = require('os');
19
+ const path = require('path');
20
+
21
+ let payload = '';
22
+ process.stdin.setEncoding('utf8');
23
+ process.stdin.on('data', chunk => { payload += chunk; });
24
+ process.stdin.on('end', main);
25
+ setTimeout(() => { if (!process.stdin.readableEnded) main(); }, 100);
26
+
27
+ function main() {
28
+ try {
29
+ const data = payload ? JSON.parse(payload) : {};
30
+ const sessionJsonl = data.session_path
31
+ || data.transcript_path
32
+ || findLatestClaudeSession(data.cwd || process.cwd());
33
+
34
+ if (!sessionJsonl || !fs.existsSync(sessionJsonl)) {
35
+ process.exit(0);
36
+ }
37
+
38
+ const { extractCandidates } = require(path.join(__dirname, '..', '..', 'scripts', 'memory', 'extract'));
39
+ const candidates = extractCandidates(sessionJsonl);
40
+ if (candidates.length === 0) {
41
+ process.exit(0);
42
+ }
43
+
44
+ const dir = process.env.KODELYTH_MEMORY_DIR
45
+ || path.join(os.homedir(), '.kodelyth', 'memory');
46
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
47
+
48
+ const queueFile = path.join(dir, 'pending-review.jsonl');
49
+ const sessionId = data.session_id || path.basename(sessionJsonl, '.jsonl');
50
+
51
+ const lines = candidates.map(c => JSON.stringify({
52
+ ...c,
53
+ session_id: sessionId,
54
+ project_path: data.cwd || process.cwd(),
55
+ queued_at: new Date().toISOString(),
56
+ }));
57
+
58
+ fs.appendFileSync(queueFile, lines.join('\n') + '\n');
59
+
60
+ // Emit advisory message so user sees something happened
61
+ process.stdout.write(JSON.stringify({
62
+ message: `Kodelyth Memory: ${candidates.length} candidate(s) queued for review. Run "/memory review-pending" to confirm.`,
63
+ }));
64
+ process.exit(0);
65
+ } catch (err) {
66
+ process.stderr.write(`kodelyth-memory capture: ${err.message}\n`);
67
+ process.exit(0);
68
+ }
69
+ }
70
+
71
+ function findLatestClaudeSession(cwd) {
72
+ try {
73
+ const projectsDir = path.join(os.homedir(), '.claude', 'projects');
74
+ if (!fs.existsSync(projectsDir)) return null;
75
+ // Project dirs are encoded paths
76
+ const encoded = '-' + cwd.replace(/\//g, '-');
77
+ const matches = fs.readdirSync(projectsDir).filter(d => d.endsWith(encoded.slice(-30)));
78
+ if (matches.length === 0) return null;
79
+ const projectDir = path.join(projectsDir, matches[0]);
80
+ const sessions = fs.readdirSync(projectDir)
81
+ .filter(f => f.endsWith('.jsonl'))
82
+ .map(f => ({ f, mtime: fs.statSync(path.join(projectDir, f)).mtimeMs }))
83
+ .sort((a, b) => b.mtime - a.mtime);
84
+ return sessions[0] ? path.join(projectDir, sessions[0].f) : null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Kodelyth ECC — Memory Inject Hook (SessionStart)
4
+ //
5
+ // Runs at the start of every Claude Code session. Reads the project root
6
+ // from the hook payload (or cwd fallback), builds the memory context block,
7
+ // and emits it as additional system context.
8
+ //
9
+ // Hook contract: prints JSON to stdout that Claude Code will merge into
10
+ // the session's system context. Exits 0 even on error — never block a
11
+ // session because memory is unavailable.
12
+ // =============================================================================
13
+
14
+ 'use strict';
15
+
16
+ const path = require('path');
17
+
18
+ let payload = {};
19
+ try {
20
+ let raw = '';
21
+ process.stdin.setEncoding('utf8');
22
+ process.stdin.on('data', chunk => { raw += chunk; });
23
+ process.stdin.on('end', () => {
24
+ try { payload = raw ? JSON.parse(raw) : {}; } catch { payload = {}; }
25
+ main();
26
+ });
27
+ // Fallback: if stdin closes immediately (no piped input), proceed
28
+ setTimeout(() => { if (!process.stdin.readableEnded) main(); }, 100);
29
+ } catch {
30
+ main();
31
+ }
32
+
33
+ function main() {
34
+ try {
35
+ const projectRoot = payload.cwd || payload.project_root || process.cwd();
36
+ const { buildContextBlock } = require(path.join(__dirname, '..', '..', 'scripts', 'memory', 'inject'));
37
+
38
+ const block = buildContextBlock({ projectRoot });
39
+ if (!block || !block.text) {
40
+ process.exit(0);
41
+ }
42
+
43
+ // Emit as additional context — non-blocking, advisory
44
+ const output = {
45
+ additionalContext: block.text,
46
+ meta: {
47
+ source: 'kodelyth-memory',
48
+ memoryCount: block.memoryCount,
49
+ projectMemoryCount: block.projectMemoryCount,
50
+ patternCount: block.patternCount,
51
+ },
52
+ };
53
+ process.stdout.write(JSON.stringify(output));
54
+ process.exit(0);
55
+ } catch (err) {
56
+ // Never crash a session because memory hook failed — log to stderr and continue
57
+ process.stderr.write(`kodelyth-memory inject: ${err.message}\n`);
58
+ process.exit(0);
59
+ }
60
+ }
package/install.ps1 CHANGED
@@ -21,7 +21,7 @@ $ErrorActionPreference = "Stop"
21
21
  # ── Banner ────────────────────────────────────────────────────────────────────
22
22
  Write-Host ""
23
23
  Write-Host " Kodelyth ECC — Production-grade AI coding agent toolkit" -ForegroundColor Cyan
24
- Write-Host " 58 agents · 187 skills · 79 commands · 18+ hooks · god-tier intent routing" -ForegroundColor Gray
24
+ Write-Host " 59 agents · 188 skills · 80 commands · 18+ hooks · intent routing · local memory" -ForegroundColor Gray
25
25
  Write-Host ""
26
26
 
27
27
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
@@ -219,11 +219,30 @@ switch ($Target) {
219
219
  Write-Host " 2. Type: /kodelyth-quickstart"
220
220
  Write-Host " 3. Or: use kodelyth-advisor"
221
221
  }
222
+ { $_ -in "windsurf-project","windsurf-home" } {
223
+ Write-Host " 1. Open the project in Windsurf"
224
+ Write-Host " 2. Cascade auto-loads .windsurfrules on every session"
225
+ Write-Host " 3. Try: use kodelyth-advisor"
226
+ }
227
+ "cursor-project" {
228
+ Write-Host " 1. Open the project in Cursor"
229
+ Write-Host " 2. Rules and skills are now active in chat"
230
+ Write-Host " 3. Try: use kodelyth-advisor"
231
+ }
232
+ "codex-home" {
233
+ Write-Host " 1. Restart Codex CLI (codex)"
234
+ Write-Host " 2. All 59 agents and 188 skills are now available"
235
+ Write-Host " 3. Try: use kodelyth-advisor"
236
+ }
222
237
  "antigravity" {
223
238
  Write-Host " 1. Open your project in Antigravity"
224
239
  Write-Host " 2. Agents are available as Skills"
225
240
  Write-Host " 3. Commands are available as Workflows"
226
241
  }
242
+ "opencode" {
243
+ Write-Host " 1. Open the project in OpenCode"
244
+ Write-Host " 2. Rules in .opencode/rules/ are now loaded"
245
+ }
227
246
  default {
228
247
  Write-Host " 1. Restart your AI coding agent"
229
248
  Write-Host " 2. Agents, skills, and rules are now active"
package/install.sh CHANGED
@@ -47,7 +47,7 @@ echo -e "${RED}${BOLD} ║ DANGER LEVEL: GOD TIER · NOT FOR JUNIOR DEV
47
47
  echo -e "${RED}${BOLD} ╚══════════════════════════════════════════════════════════════╝${RESET}"
48
48
  echo ""
49
49
  echo -e "${BOLD} Kodelyth ECC — The most dangerous AI coding toolkit on the planet${RESET}"
50
- echo -e "${CYAN} 58 specialist agents · 187 skills · 79 commands · 18+ hooks · god-tier intent routing${RESET}"
50
+ echo -e "${CYAN} 59 specialist agents · 188 skills · 80 commands · 18+ hooks · intent routing · local memory${RESET}"
51
51
  echo -e "${CYAN} Any language · Any framework · Any scale · 300B-level quality${RESET}"
52
52
  echo ""
53
53
  echo -e " github.com/sifxprime/kodelyth-ecc"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "1.3.0",
4
- "description": "Production-grade AI coding toolkit — 58 agents, 187 skills, 79 commands, god-tier intent routing. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, and OpenCode.",
3
+ "version": "1.4.1",
4
+ "description": "Production-grade AI coding toolkit — 59 agents, 188 skills, 80 commands, god-tier intent routing, local self-learning memory with auto chat detection. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, and OpenCode.",
5
5
  "author": "Kodelyth <github.com/sifxprime>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -19,6 +19,9 @@
19
19
  "ai-agents",
20
20
  "coding-toolkit",
21
21
  "llm",
22
+ "memory",
23
+ "self-learning",
24
+ "auto-recall",
22
25
  "kodelyth"
23
26
  ],
24
27
  "bin": {
@@ -0,0 +1,56 @@
1
+ # Kodelyth Memory Protocol
2
+
3
+ > Auto-loaded every session. Tells the AI when to consult and contribute to local memory.
4
+
5
+ ## What is Kodelyth Memory
6
+
7
+ A local file at `~/.kodelyth/memory/memories.jsonl` storing solutions, patterns, and gotchas extracted from past sessions. Retrieval is BM25 (keyword + tag matching). It is **not** a learned model — it is a retrieval store that gives you better context.
8
+
9
+ If the SessionStart hook ran, you have already received a memory block in your initial context with:
10
+ - The user's recurring patterns (tags seen across multiple sessions)
11
+ - Recent solutions in this project
12
+ - Their detected stack
13
+
14
+ ## When to recall memory mid-session
15
+
16
+ Trigger a memory recall (run the `kodelyth-memory` agent or `node scripts/memory/cli.js search "<query>"`) when **any** of these is true:
17
+
18
+ 1. The user describes a task in a domain that matches a tag in their memory (`payments`, `auth`, `database`, `deployment`, etc.)
19
+ 2. The user asks "have I done this before?" or "how did I solve X last time?"
20
+ 3. The user mentions a library, framework, or service by name that appears in past memories
21
+ 4. You're about to commit to an architectural decision and want to check past precedent
22
+
23
+ ## How to surface a recalled memory
24
+
25
+ Naturally, never robotically. Pattern:
26
+
27
+ > I checked your memory — you solved a similar problem in `<project>` on `<date>`. The approach that worked was `<approach>`. Want me to apply the same here, or is this case different?
28
+
29
+ Always end by giving the user the option to override. Memory is a hint, not a command.
30
+
31
+ ## When to capture a new memory
32
+
33
+ Capture when **all** are true:
34
+ - The user signals success (`"that worked"`, `"perfect"`, `"fixed it"`, `"thanks"`, `"done"`)
35
+ - The work involved real iteration (not a one-shot trivial fix)
36
+ - The lesson is reusable (would help in another similar problem, not just this exact file)
37
+
38
+ Show the user the draft memory before storing. Never silently capture.
39
+
40
+ ## What you must not do
41
+
42
+ - **Never** auto-recall the same memory twice in one session (the user has seen it)
43
+ - **Never** capture without explicit confirmation (`yes`, `store it`, `remember`)
44
+ - **Never** treat a recalled memory as ground truth — it could be stale
45
+ - **Never** transmit memory data anywhere — it is local-only by design
46
+ - **Never** fabricate a memory ("you usually...") if no relevant memory exists — say nothing
47
+
48
+ ## Cache-aware behaviour
49
+
50
+ The injected memory block is structured so its prefix is identical across calls in the same project. Do not rewrite or reorder this block during a session — that defeats the prompt cache and burns tokens. If you need to add session-specific notes, append them after the block.
51
+
52
+ ## Honest disclosure
53
+
54
+ If the user asks "how do you know that about me?", answer plainly:
55
+
56
+ > "It's in your local Kodelyth Memory at `~/.kodelyth/memory/`. You can inspect it, edit it, or delete it any time. Nothing was sent anywhere."
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Kodelyth ECC — Memory CLI
4
+ //
5
+ // Usage:
6
+ // node scripts/memory/cli.js list
7
+ // node scripts/memory/cli.js search "<query>"
8
+ // node scripts/memory/cli.js remember "<title>" --approach "<text>" --tags tag1,tag2
9
+ // node scripts/memory/cli.js forget <id>
10
+ // node scripts/memory/cli.js stats
11
+ // node scripts/memory/cli.js inject [--query "<text>"]
12
+ // node scripts/memory/cli.js extract <session.jsonl>
13
+ // node scripts/memory/cli.js rebuild-index
14
+ // =============================================================================
15
+
16
+ 'use strict';
17
+
18
+ const path = require('path');
19
+ const store = require('./store');
20
+ const { buildContextBlock } = require('./inject');
21
+ const { extractCandidates } = require('./extract');
22
+
23
+ function parseArgs(argv) {
24
+ const args = argv.slice(2);
25
+ const cmd = args[0];
26
+ const positional = [];
27
+ const flags = {};
28
+ for (let i = 1; i < args.length; i++) {
29
+ const a = args[i];
30
+ if (a.startsWith('--')) {
31
+ const key = a.slice(2);
32
+ const next = args[i + 1];
33
+ if (!next || next.startsWith('--')) {
34
+ flags[key] = true;
35
+ } else {
36
+ flags[key] = next;
37
+ i += 1;
38
+ }
39
+ } else {
40
+ positional.push(a);
41
+ }
42
+ }
43
+ return { cmd, positional, flags };
44
+ }
45
+
46
+ function help() {
47
+ console.log(`
48
+ Kodelyth ECC — Memory CLI
49
+
50
+ Commands:
51
+ list Show all stored memories
52
+ search "<query>" BM25 search across memories
53
+ remember "<title>" Add a memory (use --approach, --tags, --language)
54
+ forget <id> Mark a memory deleted
55
+ stats Show memory store stats
56
+ inject [--query "<text>"] Print the cache-friendly context block
57
+ extract <session.jsonl> Extract memory candidates from a session log
58
+ rebuild-index Rebuild the BM25 index from memories.jsonl
59
+
60
+ Storage: ${store.PATHS.dir}
61
+ `);
62
+ }
63
+
64
+ function fmt(memory, full = false) {
65
+ const date = (memory.captured_at || '').slice(0, 10);
66
+ const tags = (memory.tags || []).join(',');
67
+ if (!full) {
68
+ return `${memory.id} ${date} [${memory.language || '-'}] ${memory.problem.slice(0, 70)} (${tags})`;
69
+ }
70
+ return [
71
+ `id: ${memory.id}`,
72
+ `captured: ${memory.captured_at}`,
73
+ `language: ${memory.language || '-'}`,
74
+ `tags: ${tags || '-'}`,
75
+ `project: ${memory.project_path || '-'}`,
76
+ `problem: ${memory.problem}`,
77
+ `approach:`,
78
+ ...(memory.approach || '').split('\n').map(l => ` ${l}`),
79
+ memory.gotchas?.length ? `gotchas:` : '',
80
+ ...(memory.gotchas || []).map(g => ` - ${g}`),
81
+ ].filter(Boolean).join('\n');
82
+ }
83
+
84
+ function main() {
85
+ const { cmd, positional, flags } = parseArgs(process.argv);
86
+
87
+ if (!cmd || cmd === 'help' || cmd === '-h' || cmd === '--help') {
88
+ help();
89
+ return;
90
+ }
91
+
92
+ switch (cmd) {
93
+ case 'list': {
94
+ const all = store.listAll();
95
+ if (all.length === 0) {
96
+ console.log('No memories yet. Add one with: memory remember "<title>" --approach "<text>"');
97
+ return;
98
+ }
99
+ for (const m of all) console.log(fmt(m));
100
+ console.log(`\n${all.length} total`);
101
+ return;
102
+ }
103
+
104
+ case 'search': {
105
+ const query = positional[0];
106
+ if (!query) { console.error('Usage: search "<query>"'); process.exit(1); }
107
+ const results = store.recall(query, { limit: Number(flags.limit) || 5 });
108
+ if (results.length === 0) { console.log('No matches.'); return; }
109
+ for (const m of results) {
110
+ console.log(`[score ${m.score.toFixed(2)}] ${fmt(m)}`);
111
+ }
112
+ return;
113
+ }
114
+
115
+ case 'remember': {
116
+ const title = positional[0];
117
+ const approach = flags.approach;
118
+ if (!title || !approach) {
119
+ console.error('Usage: remember "<title>" --approach "<what worked>" [--tags a,b] [--language ts]');
120
+ process.exit(1);
121
+ }
122
+ const memory = store.capture({
123
+ problem: title,
124
+ approach,
125
+ tags: (flags.tags || '').split(',').filter(Boolean),
126
+ project: flags.project || process.cwd(),
127
+ language: flags.language || null,
128
+ files: (flags.files || '').split(',').filter(Boolean),
129
+ gotchas: (flags.gotchas || '').split(';').filter(Boolean),
130
+ source: 'cli',
131
+ });
132
+ console.log(`Captured: ${memory.id}`);
133
+ return;
134
+ }
135
+
136
+ case 'forget': {
137
+ const id = positional[0];
138
+ if (!id) { console.error('Usage: forget <id>'); process.exit(1); }
139
+ const ok = store.forget(id);
140
+ console.log(ok ? `Forgotten: ${id}` : `Not found: ${id}`);
141
+ return;
142
+ }
143
+
144
+ case 'stats': {
145
+ const s = store.stats();
146
+ console.log(`Total memories: ${s.total}`);
147
+ console.log(`Storage: ${s.storageDir}`);
148
+ console.log(`Projects: ${s.projects}`);
149
+ console.log(`By language:`);
150
+ for (const [lang, count] of Object.entries(s.byLanguage)) {
151
+ console.log(` ${lang.padEnd(12)} ${count}`);
152
+ }
153
+ console.log(`Top tags:`);
154
+ for (const [tag, count] of s.topTags) {
155
+ console.log(` ${tag.padEnd(20)} ${count}`);
156
+ }
157
+ return;
158
+ }
159
+
160
+ case 'inject': {
161
+ const block = buildContextBlock({
162
+ projectRoot: flags.project || process.cwd(),
163
+ query: flags.query || null,
164
+ });
165
+ if (!block) { console.log(''); return; }
166
+ console.log(block.text);
167
+ return;
168
+ }
169
+
170
+ case 'extract': {
171
+ const sessionPath = positional[0];
172
+ if (!sessionPath) { console.error('Usage: extract <session.jsonl>'); process.exit(1); }
173
+ const candidates = extractCandidates(path.resolve(sessionPath));
174
+ if (candidates.length === 0) { console.log('No memory candidates found.'); return; }
175
+ console.log(`Found ${candidates.length} candidate memories:\n`);
176
+ candidates.forEach((c, i) => {
177
+ console.log(`[${i + 1}] (score ${c.score}) ${c.problem}`);
178
+ console.log(` tags: ${c.tags.join(', ') || '-'}`);
179
+ console.log(` language: ${c.language || '-'}`);
180
+ console.log(` approach: ${c.approach.slice(0, 200)}...`);
181
+ console.log('');
182
+ });
183
+ console.log('Review and confirm with: memory remember "<problem>" --approach "<approach>"');
184
+ return;
185
+ }
186
+
187
+ case 'rebuild-index': {
188
+ const r = store.rebuildIndex();
189
+ console.log(`Rebuilt index for ${r.count} memories.`);
190
+ return;
191
+ }
192
+
193
+ default:
194
+ console.error(`Unknown command: ${cmd}`);
195
+ help();
196
+ process.exit(1);
197
+ }
198
+ }
199
+
200
+ main();