kodelyth-ecc 2.5.3 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.5.4 — Memory: direct capture CLI + aggressive auto-capture (July 2026)
6
+
7
+ Two memory improvements after an end-to-end verification pass confirmed the pipeline works but only captured when the user said "thanks".
8
+
9
+ ### Added
10
+
11
+ - **`kodelythecc memory` subcommand** — manage the local BM25 memory directly:
12
+ - `memory capture --problem "…" --approach "…" [--tags a,b] [--language ts] [--files a,b]` — store a fix on the spot, **no review queue**
13
+ - `memory recall "<query>" [--limit N]` — BM25 search
14
+ - `memory list` · `memory stats`
15
+ - Wired into `--help` (`kodelythecc memory --help`).
16
+
17
+ ### Changed
18
+
19
+ - **Auto-capture is now more aggressive.** Previously it only queued a memory when a user message contained a success phrase ("that worked", "perfect", "thanks"). It now **also** captures on a real verification signal — an `Edit`/`Write` followed by a passing test or successful build (`exit code 0`, `tests passed`, `build succeeded`, `PASS`, …) — even without a spoken acknowledgement. This catches fixes you verified but never verbally confirmed. Opt out with `KODELYTH_CAPTURE_AGGRESSIVE=0`.
20
+
21
+ ### Verified
22
+
23
+ - End-to-end: capture → persist → recall across processes; `auto-recall` hook injects past fixes; `auto-capture` hook queues from a real `.message`-nested transcript; `doctor` memory checks pass.
24
+ - New regression tests: aggressive path captures without "thanks"; opt-out flag disables it. Full suite 0 failures.
25
+
5
26
  ## v2.5.3 — Devil Mode + Memory cards for "How it works" (July 2026)
6
27
 
7
28
  ### Added
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.5.3
1
+ 2.5.4
@@ -118,6 +118,21 @@ if (args[1] === '--help' || args[1] === '-h') {
118
118
  install target, hook registration, memory recall, MCP registration + boot,
119
119
  prompt-injection guard, RTK, Terse, codebase graph. Run it if anything
120
120
  feels off — it pinpoints exactly what's not wired and how to fix it.
121
+ `,
122
+ memory: `
123
+ kodelyth-ecc memory — local BM25 self-learning memory
124
+
125
+ Usage:
126
+ kodelythecc memory capture --problem "<what was hard>" --approach "<what worked>" [--tags a,b] [--language ts] [--files a,b]
127
+ kodelythecc memory recall "<query>" [--limit N]
128
+ kodelythecc memory list
129
+ kodelythecc memory stats
130
+
131
+ 'capture' stores directly (no review queue) — use it to remember a fix on
132
+ the spot. Auto-recall/auto-capture also run via hooks in your AI tool.
133
+ Storage: ~/.kodelythecc/memory/ · 100% local, zero telemetry.
134
+ Auto-capture aggressiveness: it also captures on edit + passing test even
135
+ without a spoken "thanks"; set KODELYTH_CAPTURE_AGGRESSIVE=0 to opt out.
121
136
  `,
122
137
  };
123
138
  const cmd = args[0];
@@ -353,6 +368,62 @@ if (args[0] === 'rtk') {
353
368
 
354
369
  // ── Subcommand: doctor (live subsystem health check) ─────────────────────────
355
370
  // Usage: kodelythecc doctor [--json]
371
+ // ── Subcommand: memory (direct capture / recall / stats, no review queue) ────
372
+ // Usage:
373
+ // kodelythecc memory capture --problem "..." --approach "..." [--tags a,b] [--language ts]
374
+ // kodelythecc memory recall "<query>" [--limit N]
375
+ // kodelythecc memory list
376
+ // kodelythecc memory stats
377
+ if (args[0] === 'memory') {
378
+ const store = require(path.join(ROOT, 'scripts', 'memory', 'store.js'));
379
+ const sub = args[1] || 'stats';
380
+ const rest = args.slice(2);
381
+ function flag(name, dflt) { const i = rest.indexOf('--' + name); return i >= 0 && rest[i + 1] && !rest[i + 1].startsWith('--') ? rest[i + 1] : dflt; }
382
+ const w = (m) => process.stdout.write(m + '\n');
383
+ try {
384
+ if (sub === 'capture' || sub === 'remember') {
385
+ const problem = flag('problem') || rest.find(a => !a.startsWith('--'));
386
+ const approach = flag('approach');
387
+ if (!problem || !approach) {
388
+ process.stderr.write('usage: kodelythecc memory capture --problem "<what was hard>" --approach "<what worked>" [--tags a,b] [--language ts] [--files a,b]\n');
389
+ process.exit(2);
390
+ }
391
+ const m = store.capture({
392
+ problem, approach,
393
+ tags: (flag('tags') || '').split(',').filter(Boolean),
394
+ language: flag('language') || null,
395
+ files: (flag('files') || '').split(',').filter(Boolean),
396
+ project: flag('project') || process.cwd(),
397
+ source: 'cli',
398
+ });
399
+ w(`✓ stored ${m.id} — "${m.problem.slice(0, 60)}"`);
400
+ process.exit(0);
401
+ }
402
+ if (sub === 'recall' || sub === 'search') {
403
+ const query = rest.find(a => !a.startsWith('--'));
404
+ if (!query) { process.stderr.write('usage: kodelythecc memory recall "<query>"\n'); process.exit(2); }
405
+ const results = store.recall(query, { limit: Number(flag('limit')) || 5, minScore: 0.1 });
406
+ if (!results.length) { w('No matching memories.'); process.exit(0); }
407
+ for (const m of results) w(`[${(m.score || 0).toFixed(2)}] ${m.problem}\n → ${(m.approach || '').split('\n')[0].slice(0, 80)} (${(m.tags || []).join(', ')})`);
408
+ process.exit(0);
409
+ }
410
+ if (sub === 'list') {
411
+ const all = store.listAll();
412
+ w(`${all.length} memories:`);
413
+ for (const m of all.slice(0, 50)) w(` ${m.id} ${(m.captured_at || '').slice(0, 10)} ${(m.problem || '').slice(0, 60)}`);
414
+ process.exit(0);
415
+ }
416
+ if (sub === 'stats') {
417
+ const s = store.stats();
418
+ w(`Memories: ${s.total} · projects: ${s.projects} · dir: ${s.storageDir}`);
419
+ w(`Top tags: ${s.topTags.slice(0, 8).map(t => t[0] + '(' + t[1] + ')').join(', ')}`);
420
+ process.exit(0);
421
+ }
422
+ process.stderr.write('unknown memory subcommand. try: capture | recall | list | stats\n');
423
+ process.exit(2);
424
+ } catch (e) { process.stderr.write(`[memory] ${e.message}\n`); process.exit(1); }
425
+ }
426
+
356
427
  if (args[0] === 'doctor') {
357
428
  const { run, PASS, WARN, FAIL } = require(path.join(ROOT, 'scripts', 'doctor-health.js'));
358
429
  const report = run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.5.3",
3
+ "version": "2.5.4",
4
4
  "description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 194 skills, 97 commands, parallel multi-agent commands, semantic intent routing, self-learning memory, and a built-in MCP server (16 tools / 6 prompts / 377 resources) that bridges to Claude Desktop, LangGraph, AutoGen, CrewAI, and OpenAI Agents SDK. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, RooCode, Aider, Kimi, and Gemini CLI.",
5
5
  "author": "Kodelyth <github.com/sifxprime>",
6
6
  "license": "MIT",
@@ -172,19 +172,11 @@ function extractCandidates(jsonlPath) {
172
172
  }
173
173
  }
174
174
 
175
- // Find user success messages those mark candidate moments
176
- for (let i = 0; i < events.length; i++) {
177
- const ev = events[i];
178
- if (ev.role !== 'user') continue;
179
- const text = extractText(ev);
180
- if (!SUCCESS_PHRASES.some(rx => rx.test(text))) continue;
181
-
182
- const score = scoreCandidate(events, i);
183
- if (score < 3) continue;
184
-
185
- // Look back to find the problem and approach
186
- const window = events.slice(Math.max(0, i - 20), i);
187
- const problemEvent = window.find(e => e.role === 'user');
175
+ // Build a candidate from the window of events before a "solved" moment at
176
+ // index `i`. Returns null if there's no clear problem + approach.
177
+ function buildAt(i, score) {
178
+ const window = events.slice(Math.max(0, i - 20), i + 1);
179
+ const problemEvent = window.find(e => e.role === 'user' && extractText(e).trim());
188
180
  const problem = problemEvent ? extractText(problemEvent).split('\n')[0].slice(0, 280) : null;
189
181
 
190
182
  const filesTouched = Array.from(new Set(
@@ -194,26 +186,48 @@ function extractCandidates(jsonlPath) {
194
186
  .filter(Boolean)
195
187
  )).slice(0, 5);
196
188
 
197
- // Find the last assistant message that actually has TEXT — skip trailing
198
- // tool_use/tool_result events which carry no explanation of the fix.
199
- const reversed = window.slice().reverse();
189
+ // Last assistant message with actual explanatory TEXT.
200
190
  let approach = null;
201
- for (const e of reversed) {
191
+ for (const e of window.slice().reverse()) {
202
192
  if (e.role !== 'assistant') continue;
203
193
  const t = extractText(e).trim();
204
194
  if (t) { approach = t.slice(0, 600); break; }
205
195
  }
206
-
207
- if (!problem || !approach) continue;
208
-
209
- candidates.push({
210
- problem,
211
- approach,
196
+ if (!problem || !approach) return null;
197
+ return {
198
+ problem, approach,
212
199
  tags: extractTags(`${problem} ${approach}`),
213
200
  files: filesTouched,
214
201
  language: detectLanguage(filesTouched),
215
202
  score,
216
- });
203
+ };
204
+ }
205
+
206
+ const PASS_SIGNAL = /\bexit code 0\b|\btests? passed\b|\ball (tests )?pass(ed)?\b|\b0 failing\b|\bbuild succeeded\b|✓|PASS\b/i;
207
+
208
+ for (let i = 0; i < events.length; i++) {
209
+ const ev = events[i];
210
+
211
+ // Path 1 — user says it worked ("that worked", "perfect", "thanks", ...).
212
+ if (ev.role === 'user' && SUCCESS_PHRASES.some(rx => rx.test(extractText(ev)))) {
213
+ const score = scoreCandidate(events, i);
214
+ if (score >= 3) { const c = buildAt(i, score); if (c) candidates.push(c); }
215
+ continue;
216
+ }
217
+
218
+ // Path 2 (aggressive, v2.5.4+) — a passing test / successful build that
219
+ // FOLLOWS at least one edit, even without an explicit user "thanks". This
220
+ // catches real fixes the user verified but never verbally acknowledged.
221
+ // Opt out with KODELYTH_CAPTURE_AGGRESSIVE=0.
222
+ if (process.env.KODELYTH_CAPTURE_AGGRESSIVE !== '0'
223
+ && PASS_SIGNAL.test(extractText(ev))) {
224
+ const priorWindow = events.slice(Math.max(0, i - 12), i);
225
+ const hadEdit = priorWindow.some(e => e.tool_name === 'Edit' || e.tool_name === 'Write');
226
+ if (hadEdit) {
227
+ const score = scoreCandidate(events, i) + 2; // verification is a strong signal
228
+ if (score >= 3) { const c = buildAt(i, score); if (c) candidates.push(c); }
229
+ }
230
+ }
217
231
  }
218
232
 
219
233
  // Dedupe by problem