kodelyth-ecc 1.7.5 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -3
  2. package/CLAUDE.md +1 -1
  3. package/README.md +8 -9
  4. package/SECURITY.md +5 -6
  5. package/bundles/enterprise.md +1 -1
  6. package/bundles/indie-hacker.md +1 -1
  7. package/bundles/red-team.md +1 -1
  8. package/hooks/hooks.json +13 -0
  9. package/hooks/memory/auto-resolve.js +68 -0
  10. package/hooks/memory/capture-correction.js +16 -0
  11. package/hooks/memory/capture-stop.js +70 -14
  12. package/package.json +1 -1
  13. package/rules/common/agents.md +1 -1
  14. package/rules/common/self-improvement-workflow.md +2 -2
  15. package/scripts/dashboard/data.js +1 -1
  16. package/scripts/dashboard/server.js +1 -1
  17. package/scripts/memory/instincts.js +282 -0
  18. package/scripts/memory/store.js +104 -0
  19. package/social/card-agents.svg +1 -1
  20. package/social/card-install.svg +1 -1
  21. package/social/card-main.svg +1 -1
  22. package/social/facebook-v150.svg +14 -13
  23. package/social/fb-ad-main.svg +128 -0
  24. package/social/fb-post-features.svg +118 -0
  25. package/social/fb-post-launch.svg +144 -0
  26. package/social/fb-post-platforms.svg +135 -0
  27. package/social/github-social-preview.svg +119 -28
  28. package/social/hype-compound-learning.svg +9 -9
  29. package/social/hype-devil-mode.svg +8 -8
  30. package/social/hype-mcp-server.svg +8 -8
  31. package/social/hype-parallel-agents.svg +8 -8
  32. package/social/hype-stats-hero.svg +3 -3
  33. package/social/og-image.svg +116 -30
  34. package/social/readme-hero.svg +4 -4
  35. package/social/section-mcp.svg +1 -1
  36. package/social/twitter-threads.md +3 -3
  37. package/social/x-card-agents-grid.svg +2 -2
  38. package/social/x-card-free.svg +2 -2
  39. package/social/x-card-hook.svg +5 -5
  40. package/tests/memory/instincts.test.js +258 -0
  41. package/tests/memory/store.test.js +82 -0
  42. package/wiki/FAQ.md +1 -1
  43. package/wiki/Home.md +3 -3
  44. package/wiki/Installation-Guide.md +2 -2
@@ -0,0 +1,258 @@
1
+ // Tests for scripts/memory/instincts.js — runs against a temp directory.
2
+ // Covers Improvement B (structured instinct schema), C (outcome tracking),
3
+ // and D (pattern confidence decay).
4
+ 'use strict';
5
+
6
+ const test = require('node:test');
7
+ const assert = require('node:assert/strict');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+
12
+ // ── Temp directory isolation ──────────────────────────────────────────────────
13
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-instincts-'));
14
+ process.env.KODELYTH_MEMORY_DIR = TMP;
15
+
16
+ // Require AFTER setting env
17
+ const instincts = require('../../scripts/memory/instincts');
18
+
19
+ // ── Helpers ───────────────────────────────────────────────────────────────────
20
+ const PROJECT_A = '/projects/test-a';
21
+ const PROJECT_B = '/projects/test-b';
22
+
23
+ function freshFile() {
24
+ // Wipe the instincts file before each group of tests that needs isolation
25
+ if (fs.existsSync(instincts.INSTINCTS_FILE)) {
26
+ fs.unlinkSync(instincts.INSTINCTS_FILE);
27
+ }
28
+ }
29
+
30
+ // ── captureFromCorrection ─────────────────────────────────────────────────────
31
+
32
+ test('captureFromCorrection stores a new instinct', () => {
33
+ freshFile();
34
+ const inst = instincts.captureFromCorrection('always use pnpm not npm', PROJECT_A, 'project');
35
+ assert.ok(inst.id, 'should have an id');
36
+ assert.ok(inst.rule.includes('pnpm'), 'rule should contain original text');
37
+ assert.equal(inst.scope, 'project');
38
+ assert.equal(inst.source, 'correction');
39
+ assert.ok(inst.confidence >= 0.5 && inst.confidence <= 1.0, 'confidence in range');
40
+ assert.equal(inst.use_count, 1);
41
+ assert.equal(inst.stale, false);
42
+ assert.equal(inst.outcome, null);
43
+ assert.equal(inst.project_path, PROJECT_A);
44
+ });
45
+
46
+ test('captureFromCorrection is idempotent — re-capture increments use_count', () => {
47
+ freshFile();
48
+ const first = instincts.captureFromCorrection('never use var', PROJECT_A, 'project');
49
+ const second = instincts.captureFromCorrection('never use var', PROJECT_A, 'project');
50
+ assert.equal(first.id, second.id, 'same correction should produce same id');
51
+ assert.equal(second.use_count, 2);
52
+ assert.ok(second.confidence > first.confidence, 're-capture should boost confidence');
53
+ });
54
+
55
+ test('captureFromCorrection creates distinct records for different projects', () => {
56
+ freshFile();
57
+ const a = instincts.captureFromCorrection('always use async/await', PROJECT_A, 'project');
58
+ const b = instincts.captureFromCorrection('always use async/await', PROJECT_B, 'project');
59
+ assert.notEqual(a.id, b.id, 'same rule, different project = different instinct');
60
+ });
61
+
62
+ test('captureFromCorrection caps confidence at 1.0', () => {
63
+ freshFile();
64
+ // Capture 10 times — confidence should never exceed 1.0
65
+ for (let i = 0; i < 10; i++) {
66
+ instincts.captureFromCorrection('use const not let', PROJECT_A, 'project');
67
+ }
68
+ const all = instincts.list({ projectPath: PROJECT_A });
69
+ const inst = all.find(i => i.rule.includes('const'));
70
+ assert.ok(inst, 'instinct should exist');
71
+ assert.ok(inst.confidence <= 1.0, 'confidence should not exceed 1.0');
72
+ });
73
+
74
+ // ── recordOutcome (by id) ─────────────────────────────────────────────────────
75
+
76
+ test('recordOutcome boosts confidence on success', () => {
77
+ freshFile();
78
+ const inst = instincts.captureFromCorrection('prefer interface over type for objects', PROJECT_A);
79
+ const before = inst.confidence;
80
+ const updated = instincts.recordOutcome(inst.id, 'success');
81
+ assert.ok(updated.confidence > before, 'success should boost confidence');
82
+ assert.equal(updated.outcome, 'success');
83
+ });
84
+
85
+ test('recordOutcome reduces confidence on failure', () => {
86
+ freshFile();
87
+ const inst = instincts.captureFromCorrection('avoid class components', PROJECT_A);
88
+ const before = inst.confidence;
89
+ const updated = instincts.recordOutcome(inst.id, 'failure');
90
+ assert.ok(updated.confidence < before, 'failure should reduce confidence');
91
+ assert.equal(updated.outcome, 'failure');
92
+ });
93
+
94
+ test('recordOutcome floors confidence at 0.0', () => {
95
+ freshFile();
96
+ const inst = instincts.captureFromCorrection('do not use callbacks', PROJECT_A);
97
+ // Record failure 5 times — should never go below 0
98
+ for (let i = 0; i < 5; i++) {
99
+ instincts.recordOutcome(inst.id, 'failure');
100
+ }
101
+ const updated = instincts.recordOutcome(inst.id, 'failure');
102
+ assert.ok(updated.confidence >= 0.0, 'confidence should not go below 0');
103
+ });
104
+
105
+ test('recordOutcome returns null for unknown id', () => {
106
+ freshFile();
107
+ const result = instincts.recordOutcome('nonexistent-id-xyz', 'success');
108
+ assert.equal(result, null);
109
+ });
110
+
111
+ // ── recordOutcomeByProject (Improvement C bridge) ─────────────────────────────
112
+
113
+ test('recordOutcomeByProject downgrades matching instincts by project + hint', () => {
114
+ freshFile();
115
+ instincts.captureFromCorrection('always use pnpm not npm', PROJECT_A);
116
+ instincts.captureFromCorrection('always run tests before commit', PROJECT_A);
117
+ instincts.captureFromCorrection('use tailwind not inline styles', PROJECT_B);
118
+
119
+ const before = instincts.list({ projectPath: PROJECT_A });
120
+ const pnpmBefore = before.find(i => i.rule.includes('pnpm')).confidence;
121
+
122
+ const changed = instincts.recordOutcomeByProject(PROJECT_A, 'use pnpm not npm', false);
123
+
124
+ assert.ok(changed.length >= 1, 'at least one instinct should be changed');
125
+
126
+ const after = instincts.list({ projectPath: PROJECT_A });
127
+ const pnpmAfter = after.find(i => i.rule.includes('pnpm')).confidence;
128
+ assert.ok(pnpmAfter < pnpmBefore, 'matched instinct confidence should decrease');
129
+
130
+ // PROJECT_B instinct should be untouched
131
+ const bInstincts = instincts.list({ projectPath: PROJECT_B });
132
+ assert.ok(bInstincts[0].confidence >= 0.7, 'unrelated project instinct should be unchanged');
133
+ });
134
+
135
+ test('recordOutcomeByProject skips instincts with < 20% token overlap', () => {
136
+ freshFile();
137
+ instincts.captureFromCorrection('always use pnpm', PROJECT_A);
138
+ const changed = instincts.recordOutcomeByProject(PROJECT_A, 'completely unrelated topic xyz', false);
139
+ // Should change 0 because token overlap < 20%
140
+ assert.equal(changed.length, 0, 'should not match on low overlap');
141
+ });
142
+
143
+ // ── runDecayCheck (Improvement D) ─────────────────────────────────────────────
144
+
145
+ test('runDecayCheck marks old instincts as stale', () => {
146
+ freshFile();
147
+ const inst = instincts.captureFromCorrection('prefer named exports', PROJECT_A);
148
+
149
+ // Manually backdate last_used to 35 days ago
150
+ const all = instincts.list();
151
+ const record = all.find(i => i.id === inst.id);
152
+ const old = new Date();
153
+ old.setDate(old.getDate() - 35);
154
+ record.last_used = old.toISOString();
155
+ // Write back manually via the file
156
+ const lines = fs.readFileSync(instincts.INSTINCTS_FILE, 'utf8').split('\n').filter(Boolean);
157
+ const rewritten = lines.map(l => {
158
+ try {
159
+ const r = JSON.parse(l);
160
+ return r.id === inst.id ? JSON.stringify(record) : l;
161
+ } catch { return l; }
162
+ });
163
+ fs.writeFileSync(instincts.INSTINCTS_FILE, rewritten.join('\n') + '\n', 'utf8');
164
+
165
+ const stale = instincts.runDecayCheck();
166
+ assert.ok(stale.length >= 1, 'should detect at least one stale instinct');
167
+ assert.equal(stale[0].id, inst.id);
168
+ assert.equal(stale[0].stale, true);
169
+ });
170
+
171
+ test('runDecayCheck does not mark recent instincts as stale', () => {
172
+ freshFile();
173
+ instincts.captureFromCorrection('use strict mode', PROJECT_A);
174
+ const stale = instincts.runDecayCheck();
175
+ assert.equal(stale.length, 0, 'fresh instinct should not be stale');
176
+ });
177
+
178
+ test('runDecayCheck returns only newly-stale (not already-stale)', () => {
179
+ freshFile();
180
+ const inst = instincts.captureFromCorrection('prefer optional chaining', PROJECT_A);
181
+
182
+ // Backdate and mark already stale
183
+ const lines = fs.readFileSync(instincts.INSTINCTS_FILE, 'utf8').split('\n').filter(Boolean);
184
+ const old = new Date();
185
+ old.setDate(old.getDate() - 40);
186
+ const rewritten = lines.map(l => {
187
+ try {
188
+ const r = JSON.parse(l);
189
+ if (r.id === inst.id) {
190
+ return JSON.stringify({ ...r, stale: true, last_used: old.toISOString() });
191
+ }
192
+ return l;
193
+ } catch { return l; }
194
+ });
195
+ fs.writeFileSync(instincts.INSTINCTS_FILE, rewritten.join('\n') + '\n', 'utf8');
196
+
197
+ const stale = instincts.runDecayCheck();
198
+ assert.equal(stale.length, 0, 'already-stale instincts should not be re-reported');
199
+ });
200
+
201
+ // ── pruneWeak ─────────────────────────────────────────────────────────────────
202
+
203
+ test('pruneWeak removes instincts below threshold', () => {
204
+ freshFile();
205
+ const inst = instincts.captureFromCorrection('do not shadow outer variables', PROJECT_A);
206
+
207
+ // Drive confidence below threshold via repeated failures
208
+ for (let i = 0; i < 5; i++) {
209
+ instincts.recordOutcome(inst.id, 'failure');
210
+ }
211
+
212
+ const pruned = instincts.pruneWeak(0.2);
213
+ assert.ok(pruned >= 1, 'should prune at least one weak instinct');
214
+
215
+ const remaining = instincts.list();
216
+ const found = remaining.find(i => i.id === inst.id);
217
+ assert.equal(found, undefined, 'pruned instinct should be gone');
218
+ });
219
+
220
+ test('pruneWeak keeps high-confidence instincts', () => {
221
+ freshFile();
222
+ instincts.captureFromCorrection('use es modules not commonjs', PROJECT_A);
223
+ const pruned = instincts.pruneWeak(0.2);
224
+ assert.equal(pruned, 0, 'strong instinct should not be pruned');
225
+ });
226
+
227
+ // ── list + filtering ──────────────────────────────────────────────────────────
228
+
229
+ test('list filters by scope', () => {
230
+ freshFile();
231
+ instincts.captureFromCorrection('global rule one', null, 'global');
232
+ instincts.captureFromCorrection('project rule one', PROJECT_A, 'project');
233
+
234
+ const globals = instincts.list({ scope: 'global' });
235
+ const projects = instincts.list({ scope: 'project' });
236
+ assert.ok(globals.every(i => i.scope === 'global'));
237
+ assert.ok(projects.every(i => i.scope === 'project'));
238
+ });
239
+
240
+ test('list filters by minConfidence', () => {
241
+ freshFile();
242
+ const inst = instincts.captureFromCorrection('high confidence rule', PROJECT_A);
243
+ instincts.recordOutcome(inst.id, 'success'); // boosts to ~0.85
244
+
245
+ const high = instincts.list({ minConfidence: 0.8 });
246
+ assert.ok(high.length >= 1);
247
+ assert.ok(high.every(i => i.confidence >= 0.8));
248
+ });
249
+
250
+ test('list filters by projectPath', () => {
251
+ freshFile();
252
+ instincts.captureFromCorrection('rule for project a', PROJECT_A, 'project');
253
+ instincts.captureFromCorrection('rule for project b', PROJECT_B, 'project');
254
+
255
+ const aOnly = instincts.list({ projectPath: PROJECT_A });
256
+ assert.ok(aOnly.every(i => i.project_path === PROJECT_A));
257
+ assert.ok(aOnly.length >= 1);
258
+ });
@@ -119,3 +119,85 @@ test('buildContextBlock returns structured block when memory exists', () => {
119
119
  assert.ok(result.text.includes('Kodelyth Memory'));
120
120
  assert.ok(result.memoryCount >= 1);
121
121
  });
122
+
123
+ // ── Improvement C: outcome tracking ──────────────────────────────────────────
124
+
125
+ test('resolveMemory marks a memory with outcome', () => {
126
+ // Capture a memory with a file reference
127
+ const m = store.capture({
128
+ problem: 'Auth token refresh race condition',
129
+ approach: 'Added mutex lock around token refresh logic',
130
+ tags: ['auth', 'race-condition'],
131
+ project: '/test/project-c',
132
+ files: ['/test/project-c/src/auth/refresh.ts'],
133
+ });
134
+ const ok = store.resolveMemory(m.id, false);
135
+ assert.equal(ok, true, 'resolveMemory should return true when memory found');
136
+
137
+ const all = store.listAll();
138
+ const updated = all.find(mem => mem.id === m.id);
139
+ assert.ok(updated, 'memory should still exist');
140
+ assert.equal(updated.resolved, false);
141
+ assert.ok(updated.resolved_at, 'resolved_at should be set');
142
+ });
143
+
144
+ test('resolveMemory returns false for unknown id', () => {
145
+ const ok = store.resolveMemory('totally-unknown-id-123', true);
146
+ assert.equal(ok, false);
147
+ });
148
+
149
+ test('findMemoriesForFile finds memories by exact file path', () => {
150
+ store.capture({
151
+ problem: 'Database migration ran twice',
152
+ approach: 'Added idempotency check in migration runner',
153
+ tags: ['migrations', 'database'],
154
+ project: '/test/project-d',
155
+ files: ['/test/project-d/migrations/001_users.sql'],
156
+ });
157
+ const matches = store.findMemoriesForFile('/test/project-d/migrations/001_users.sql');
158
+ assert.ok(matches.length >= 1, 'should find memory with exact file path');
159
+ });
160
+
161
+ test('findMemoriesForFile returns empty when no file match', () => {
162
+ const matches = store.findMemoriesForFile('/completely/unrelated/path/file.ts');
163
+ assert.equal(matches.length, 0);
164
+ });
165
+
166
+ test('findMemoriesForFile excludes already-resolved memories', () => {
167
+ const m = store.capture({
168
+ problem: 'Cache invalidation bug in product listing',
169
+ approach: 'Switched to event-driven cache clearing',
170
+ tags: ['cache', 'redis'],
171
+ project: '/test/project-e',
172
+ files: ['/test/project-e/src/cache/products.ts'],
173
+ });
174
+ store.resolveMemory(m.id, true); // mark as resolved
175
+
176
+ const matches = store.findMemoriesForFile('/test/project-e/src/cache/products.ts');
177
+ const found = matches.find(mem => mem.id === m.id);
178
+ assert.equal(found, undefined, 'resolved memory should not be returned');
179
+ });
180
+
181
+ test('autoResolveOnEdit marks matching memory resolved:false', () => {
182
+ const filePath = '/test/project-f/src/api/users.ts';
183
+ const m = store.capture({
184
+ problem: 'User endpoint returned 500 on empty body',
185
+ approach: 'Added body validation middleware before handler',
186
+ tags: ['api', 'validation'],
187
+ project: '/test/project-f',
188
+ files: [filePath],
189
+ });
190
+
191
+ const resolved = store.autoResolveOnEdit(filePath, '/test/project-f');
192
+ assert.ok(resolved.length >= 1, 'should resolve at least one memory');
193
+ assert.equal(resolved[0].id, m.id);
194
+
195
+ const all = store.listAll();
196
+ const updated = all.find(mem => mem.id === m.id);
197
+ assert.equal(updated.resolved, false);
198
+ });
199
+
200
+ test('autoResolveOnEdit returns empty when no matching memory', () => {
201
+ const resolved = store.autoResolveOnEdit('/no/memory/for/this/file.ts');
202
+ assert.equal(resolved.length, 0);
203
+ });
package/wiki/FAQ.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **What is Kodelyth ECC?**
8
8
 
9
- An AI coding toolkit (v1.7.5) that installs into 11 AI coding platforms (13 install targets). It adds 70 specialist agents (including 8 adversarial devil-mode agents), 194 skills, 97 slash commands, 20+ automation hooks, and a compound learning system. Zero cloud, zero telemetry, all local.
9
+ An AI coding toolkit (v1.8.0) that installs into 11 AI coding platforms (13 install targets). It adds 70 specialist agents (including 8 adversarial devil-mode agents), 194 skills, 97 slash commands, 20+ automation hooks, and a compound learning system. Zero cloud, zero telemetry, all local.
10
10
 
11
11
  **Is it free?**
12
12
 
package/wiki/Home.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Kodelyth Enhanced Coding Companion** — production-grade AI coding toolkit for Claude Code, Windsurf, Cursor, Codex CLI, Google Antigravity, and more.
4
4
 
5
- > Version: **v1.7.5** · Agents: **70** (including 8 adversarial) · Skills: **194** · Commands: **97** (including 8 parallel) · Hooks: **22+** · Platforms: **11** · Tests: **348**
5
+ > Version: **v1.8.0** · Agents: **70** (including 8 adversarial) · Skills: **194** · Commands: **97** (including 8 parallel) · Hooks: **22+** · Platforms: **11** · Tests: **348**
6
6
 
7
7
  ---
8
8
 
@@ -56,7 +56,7 @@ After install, type `/kodelyth-quickstart` in your AI tool to begin.
56
56
 
57
57
  ---
58
58
 
59
- ## Key Features (v1.7.5)
59
+ ## Key Features (v1.8.0)
60
60
 
61
61
  ### Semantic Intent Routing
62
62
 
@@ -197,7 +197,7 @@ Kodelyth ECC installs on 11 AI coding platforms. Feature depth varies — hooks
197
197
 
198
198
  | Version | Headline |
199
199
  |---|---|
200
- | **v1.7.5** | Real-time IDE-aware dashboard (Claude Code + Windsurf + Windsurf-Next + Cursor + Antigravity), cross-IDE memory protocol via MCP tools, `KODELYTH_EXTRA_IDE_WATCH` env var, 3 s SSE realtime, 348 tests |
200
+ | **v1.8.0** | Real-time IDE-aware dashboard (Claude Code + Windsurf + Windsurf-Next + Cursor + Antigravity), cross-IDE memory protocol via MCP tools, `KODELYTH_EXTRA_IDE_WATCH` env var, 3 s SSE realtime, 373 tests |
201
201
  | **v1.7.3** | Social hype pack (5 X/Twitter SVGs + 5 thread scripts), full SVG version sync, GitHub issue templates, repo polish |
202
202
  | **v1.7.0** | Adversarial devil-mode (8 agents), MCP server, local dashboard, swarm orchestrator, cost-aware routing, self-evolving memory, SLSA L3 + SBOM, 11 platforms, 194 skills |
203
203
  | **v1.6.0** | 8 adversarial devil-mode agents, power bundles, 6 new IDE platform targets, 97 commands |
@@ -276,7 +276,7 @@ export ECC_DISABLED_HOOKS=kodelyth:prompt:recall,kodelyth:post-edit:test-reminde
276
276
  npm test
277
277
  ```
278
278
 
279
- If tests fail, file an issue with the output. Most installs pass all 348 tests.
279
+ If tests fail, file an issue with the output. Most installs pass all 373 tests.
280
280
 
281
281
  ---
282
282
 
@@ -289,6 +289,6 @@ After installing, verify these work:
289
289
  - [ ] `/memory` shows your memory store
290
290
  - [ ] `npx kodelyth-ecc mcp` starts the server
291
291
  - [ ] `npx kodelyth-ecc dashboard` opens the dashboard
292
- - [ ] `npm test` passes all 348 tests (Claude Code only)
292
+ - [ ] `npm test` passes all 373 tests (Claude Code only)
293
293
 
294
294
  If any step fails, re-run the installer or file an issue.