kodelyth-ecc 1.2.2 → 1.4.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.
- package/AGENTS.md +101 -181
- package/CHANGELOG.md +67 -0
- package/CLAUDE.md +72 -63
- package/KODELYTH.md +79 -44
- package/README.md +244 -192
- package/VERSION +1 -1
- package/agents/dependency-doctor.md +120 -0
- package/agents/env-debugger.md +154 -0
- package/agents/flake-hunter.md +142 -0
- package/agents/git-rescue.md +133 -0
- package/agents/kodelyth-memory.md +87 -0
- package/agents/release-captain.md +190 -0
- package/bin/kodelyth-ecc.js +18 -12
- package/commands/memory.md +62 -0
- package/hooks/hooks.json +26 -0
- package/hooks/memory/capture-stop.js +88 -0
- package/hooks/memory/inject-start.js +60 -0
- package/install.ps1 +28 -9
- package/install.sh +11 -97
- package/package.json +4 -2
- package/rules/common/agent-intent-routing.md +337 -0
- package/rules/common/memory-protocol.md +56 -0
- package/scripts/memory/cli.js +200 -0
- package/scripts/memory/extract.js +176 -0
- package/scripts/memory/inject.js +145 -0
- package/scripts/memory/store.js +300 -0
- package/skills/agent-handoff/SKILL.md +184 -0
- package/skills/intent-routing/SKILL.md +134 -0
- package/skills/kodelyth-memory/SKILL.md +136 -0
- package/tests/memory/store.test.js +121 -0
- package/dashboard/lib/agent-tracker.js +0 -366
- package/dashboard/lib/aggregator.js +0 -119
- package/dashboard/lib/cost-calculator.js +0 -50
- package/dashboard/lib/platform-detector.js +0 -89
- package/dashboard/lib/readers/antigravity-reader.js +0 -113
- package/dashboard/lib/readers/claude-reader.js +0 -135
- package/dashboard/lib/readers/codex-reader.js +0 -192
- package/dashboard/lib/readers/cursor-reader.js +0 -135
- package/dashboard/lib/readers/opencode-reader.js +0 -201
- package/dashboard/lib/readers/windsurf-reader.js +0 -146
- package/dashboard/package.json +0 -24
- package/dashboard/public/index.html +0 -1221
- package/dashboard/server.js +0 -119
- package/scripts/agent-tracker-hook.js +0 -81
- package/social/readme-lens.svg +0 -140
- package/social/readme-savings.svg +0 -56
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — Memory Store
|
|
3
|
+
// Local, zero-dependency, model-agnostic memory for AI coding sessions.
|
|
4
|
+
//
|
|
5
|
+
// Storage layout (all in ~/.kodelyth/memory/):
|
|
6
|
+
// memories.jsonl Append-only log of every captured memory
|
|
7
|
+
// index.json Inverted index: token -> [memory ids]
|
|
8
|
+
// patterns.json User-level patterns (preferences, conventions)
|
|
9
|
+
// projects/<hash>.json Per-project memory shortcuts
|
|
10
|
+
//
|
|
11
|
+
// Retrieval: BM25 over tokenised problem + approach + tags. No embeddings,
|
|
12
|
+
// no native deps, no network. Pure JS, runs anywhere Node 18+ runs.
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
const MEMORY_DIR = process.env.KODELYTH_MEMORY_DIR
|
|
23
|
+
|| path.join(os.homedir(), '.kodelyth', 'memory');
|
|
24
|
+
|
|
25
|
+
const PATHS = {
|
|
26
|
+
dir: MEMORY_DIR,
|
|
27
|
+
log: path.join(MEMORY_DIR, 'memories.jsonl'),
|
|
28
|
+
index: path.join(MEMORY_DIR, 'index.json'),
|
|
29
|
+
patterns: path.join(MEMORY_DIR, 'patterns.json'),
|
|
30
|
+
projects: path.join(MEMORY_DIR, 'projects'),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ── Stop words (English + common code noise) ─────────────────────────────────
|
|
34
|
+
const STOP_WORDS = new Set([
|
|
35
|
+
'the','a','an','and','or','but','if','then','else','for','to','of','in','on',
|
|
36
|
+
'is','are','was','were','be','been','being','have','has','had','do','does',
|
|
37
|
+
'did','will','would','should','can','could','may','might','must','this','that',
|
|
38
|
+
'these','those','it','its','as','at','by','from','with','about','i','you','we',
|
|
39
|
+
'they','he','she','my','your','our','their','use','using','used','set','get',
|
|
40
|
+
'fix','fixed','make','made','want','need','try','tried','run','running',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
44
|
+
function ensureDir(dir) {
|
|
45
|
+
if (!fs.existsSync(dir)) {
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tokenise(text) {
|
|
51
|
+
if (!text) return [];
|
|
52
|
+
return String(text)
|
|
53
|
+
.toLowerCase()
|
|
54
|
+
.replace(/[^a-z0-9_\-/.\s]/g, ' ')
|
|
55
|
+
.split(/\s+/)
|
|
56
|
+
.filter(t => t.length >= 2 && t.length <= 40 && !STOP_WORDS.has(t));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function projectHash(projectRoot) {
|
|
60
|
+
return crypto
|
|
61
|
+
.createHash('sha256')
|
|
62
|
+
.update(String(projectRoot))
|
|
63
|
+
.digest('hex')
|
|
64
|
+
.slice(0, 12);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function newMemoryId() {
|
|
68
|
+
return crypto.randomBytes(8).toString('hex');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Index ────────────────────────────────────────────────────────────────────
|
|
72
|
+
function loadIndex() {
|
|
73
|
+
if (!fs.existsSync(PATHS.index)) {
|
|
74
|
+
return { tokens: {}, docCount: 0, avgDocLength: 0, totalLength: 0 };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(fs.readFileSync(PATHS.index, 'utf8'));
|
|
78
|
+
} catch {
|
|
79
|
+
return { tokens: {}, docCount: 0, avgDocLength: 0, totalLength: 0 };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function saveIndex(index) {
|
|
84
|
+
ensureDir(PATHS.dir);
|
|
85
|
+
fs.writeFileSync(PATHS.index, JSON.stringify(index, null, 2));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function indexMemory(index, memory) {
|
|
89
|
+
const text = `${memory.problem || ''} ${memory.approach || ''} ${(memory.tags || []).join(' ')}`;
|
|
90
|
+
const tokens = tokenise(text);
|
|
91
|
+
const length = tokens.length;
|
|
92
|
+
if (length === 0) return index;
|
|
93
|
+
|
|
94
|
+
const tokenFreq = {};
|
|
95
|
+
for (const token of tokens) {
|
|
96
|
+
tokenFreq[token] = (tokenFreq[token] || 0) + 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const [token, freq] of Object.entries(tokenFreq)) {
|
|
100
|
+
if (!index.tokens[token]) {
|
|
101
|
+
index.tokens[token] = { docs: [], df: 0 };
|
|
102
|
+
}
|
|
103
|
+
index.tokens[token].docs.push({ id: memory.id, tf: freq, len: length });
|
|
104
|
+
index.tokens[token].df += 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
index.totalLength += length;
|
|
108
|
+
index.docCount += 1;
|
|
109
|
+
index.avgDocLength = index.totalLength / index.docCount;
|
|
110
|
+
|
|
111
|
+
return index;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── BM25 retrieval (k1=1.5, b=0.75) ──────────────────────────────────────────
|
|
115
|
+
function search(query, options = {}) {
|
|
116
|
+
const { limit = 5, minScore = 0.5, projectFilter = null } = options;
|
|
117
|
+
const index = loadIndex();
|
|
118
|
+
const tokens = tokenise(query);
|
|
119
|
+
if (tokens.length === 0 || index.docCount === 0) return [];
|
|
120
|
+
|
|
121
|
+
const k1 = 1.5;
|
|
122
|
+
const b = 0.75;
|
|
123
|
+
const N = index.docCount;
|
|
124
|
+
const avgDl = index.avgDocLength || 1;
|
|
125
|
+
const scores = {};
|
|
126
|
+
|
|
127
|
+
for (const token of tokens) {
|
|
128
|
+
const entry = index.tokens[token];
|
|
129
|
+
if (!entry) continue;
|
|
130
|
+
const idf = Math.log(1 + (N - entry.df + 0.5) / (entry.df + 0.5));
|
|
131
|
+
for (const doc of entry.docs) {
|
|
132
|
+
const norm = 1 - b + b * (doc.len / avgDl);
|
|
133
|
+
const score = idf * ((doc.tf * (k1 + 1)) / (doc.tf + k1 * norm));
|
|
134
|
+
scores[doc.id] = (scores[doc.id] || 0) + score;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const ranked = Object.entries(scores)
|
|
139
|
+
.filter(([, score]) => score >= minScore)
|
|
140
|
+
.sort(([, a], [, b]) => b - a)
|
|
141
|
+
.slice(0, limit * 3);
|
|
142
|
+
|
|
143
|
+
if (ranked.length === 0) return [];
|
|
144
|
+
|
|
145
|
+
const memories = readMemories(ranked.map(([id]) => id));
|
|
146
|
+
let results = ranked
|
|
147
|
+
.map(([id, score]) => {
|
|
148
|
+
const memory = memories[id];
|
|
149
|
+
return memory ? { ...memory, score } : null;
|
|
150
|
+
})
|
|
151
|
+
.filter(Boolean);
|
|
152
|
+
|
|
153
|
+
if (projectFilter) {
|
|
154
|
+
results = results.filter(m => m.project === projectFilter);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return results.slice(0, limit);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Memory I/O ───────────────────────────────────────────────────────────────
|
|
161
|
+
function readMemories(ids = null) {
|
|
162
|
+
if (!fs.existsSync(PATHS.log)) return ids ? {} : [];
|
|
163
|
+
const wantSet = ids ? new Set(ids) : null;
|
|
164
|
+
const lines = fs.readFileSync(PATHS.log, 'utf8').split('\n').filter(Boolean);
|
|
165
|
+
const out = ids ? {} : [];
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
let memory;
|
|
168
|
+
try { memory = JSON.parse(line); } catch { continue; }
|
|
169
|
+
if (memory.deleted) continue;
|
|
170
|
+
if (wantSet) {
|
|
171
|
+
if (wantSet.has(memory.id)) out[memory.id] = memory;
|
|
172
|
+
} else {
|
|
173
|
+
out.push(memory);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function appendMemory(memory) {
|
|
180
|
+
ensureDir(PATHS.dir);
|
|
181
|
+
fs.appendFileSync(PATHS.log, JSON.stringify(memory) + '\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
185
|
+
function capture({
|
|
186
|
+
problem,
|
|
187
|
+
approach,
|
|
188
|
+
tags = [],
|
|
189
|
+
project = null,
|
|
190
|
+
language = null,
|
|
191
|
+
files = [],
|
|
192
|
+
gotchas = [],
|
|
193
|
+
source = 'manual',
|
|
194
|
+
}) {
|
|
195
|
+
if (!problem || !approach) {
|
|
196
|
+
throw new Error('capture requires both `problem` and `approach`');
|
|
197
|
+
}
|
|
198
|
+
const memory = {
|
|
199
|
+
id: newMemoryId(),
|
|
200
|
+
captured_at: new Date().toISOString(),
|
|
201
|
+
problem: String(problem).slice(0, 500),
|
|
202
|
+
approach: String(approach).slice(0, 2000),
|
|
203
|
+
tags: Array.from(new Set(tags.map(String))).slice(0, 20),
|
|
204
|
+
project: project ? projectHash(project) : null,
|
|
205
|
+
project_path: project,
|
|
206
|
+
language,
|
|
207
|
+
files: files.slice(0, 20),
|
|
208
|
+
gotchas: gotchas.slice(0, 10),
|
|
209
|
+
source,
|
|
210
|
+
};
|
|
211
|
+
appendMemory(memory);
|
|
212
|
+
const index = loadIndex();
|
|
213
|
+
saveIndex(indexMemory(index, memory));
|
|
214
|
+
return memory;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function recall(query, options = {}) {
|
|
218
|
+
return search(query, options);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function recallForProject(projectRoot, query, options = {}) {
|
|
222
|
+
const opts = { ...options, projectFilter: projectHash(projectRoot) };
|
|
223
|
+
const results = search(query, opts);
|
|
224
|
+
if (results.length >= (options.limit || 5)) return results;
|
|
225
|
+
// Fall back to global memories if project-specific are sparse
|
|
226
|
+
const globalResults = search(query, options);
|
|
227
|
+
const seen = new Set(results.map(r => r.id));
|
|
228
|
+
for (const m of globalResults) {
|
|
229
|
+
if (!seen.has(m.id)) results.push(m);
|
|
230
|
+
if (results.length >= (options.limit || 5)) break;
|
|
231
|
+
}
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function listAll() {
|
|
236
|
+
return readMemories();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function forget(memoryId) {
|
|
240
|
+
if (!fs.existsSync(PATHS.log)) return false;
|
|
241
|
+
const lines = fs.readFileSync(PATHS.log, 'utf8').split('\n').filter(Boolean);
|
|
242
|
+
let found = false;
|
|
243
|
+
const updated = lines.map(line => {
|
|
244
|
+
try {
|
|
245
|
+
const m = JSON.parse(line);
|
|
246
|
+
if (m.id === memoryId) {
|
|
247
|
+
found = true;
|
|
248
|
+
return JSON.stringify({ ...m, deleted: true, deleted_at: new Date().toISOString() });
|
|
249
|
+
}
|
|
250
|
+
return line;
|
|
251
|
+
} catch {
|
|
252
|
+
return line;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
fs.writeFileSync(PATHS.log, updated.join('\n') + '\n');
|
|
256
|
+
if (found) rebuildIndex();
|
|
257
|
+
return found;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function rebuildIndex() {
|
|
261
|
+
const memories = readMemories();
|
|
262
|
+
let index = { tokens: {}, docCount: 0, avgDocLength: 0, totalLength: 0 };
|
|
263
|
+
for (const m of memories) {
|
|
264
|
+
index = indexMemory(index, m);
|
|
265
|
+
}
|
|
266
|
+
saveIndex(index);
|
|
267
|
+
return { count: memories.length };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function stats() {
|
|
271
|
+
const memories = readMemories();
|
|
272
|
+
const byProject = {};
|
|
273
|
+
const byLanguage = {};
|
|
274
|
+
const byTag = {};
|
|
275
|
+
for (const m of memories) {
|
|
276
|
+
if (m.project) byProject[m.project] = (byProject[m.project] || 0) + 1;
|
|
277
|
+
if (m.language) byLanguage[m.language] = (byLanguage[m.language] || 0) + 1;
|
|
278
|
+
for (const tag of m.tags || []) byTag[tag] = (byTag[tag] || 0) + 1;
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
total: memories.length,
|
|
282
|
+
storageDir: PATHS.dir,
|
|
283
|
+
projects: Object.keys(byProject).length,
|
|
284
|
+
byLanguage,
|
|
285
|
+
topTags: Object.entries(byTag).sort(([, a], [, b]) => b - a).slice(0, 10),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
PATHS,
|
|
291
|
+
capture,
|
|
292
|
+
recall,
|
|
293
|
+
recallForProject,
|
|
294
|
+
listAll,
|
|
295
|
+
forget,
|
|
296
|
+
rebuildIndex,
|
|
297
|
+
stats,
|
|
298
|
+
tokenise,
|
|
299
|
+
projectHash,
|
|
300
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-handoff
|
|
3
|
+
description: >
|
|
4
|
+
How to chain ECC specialist agents for multi-step problems —
|
|
5
|
+
pair-programmer → tdd-guide → code-reviewer → security-reviewer.
|
|
6
|
+
Use when one agent finishes its job and the next logical step needs
|
|
7
|
+
a different specialist. Documents the standard handoff protocol so
|
|
8
|
+
agents pass context cleanly.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Agent Handoff Protocol
|
|
12
|
+
|
|
13
|
+
Single agents handle single concerns. Real engineering tasks span multiple concerns. **Handoffs** are how ECC chains agents without losing context.
|
|
14
|
+
|
|
15
|
+
## The 3-Line Handoff Protocol
|
|
16
|
+
|
|
17
|
+
When an agent finishes its scope and the next step needs a different specialist, it ends with exactly this shape:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
─────────────────────────────────────
|
|
21
|
+
HANDOFF
|
|
22
|
+
From: <current-agent>
|
|
23
|
+
To: <next-agent>
|
|
24
|
+
Why: <one-line reason>
|
|
25
|
+
Carry: <what the next agent needs to know>
|
|
26
|
+
─────────────────────────────────────
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then **stop**. The next agent picks up from `Carry:`.
|
|
30
|
+
|
|
31
|
+
## Standard Chains
|
|
32
|
+
|
|
33
|
+
These are the most common multi-agent flows. Memorize them.
|
|
34
|
+
|
|
35
|
+
### Build something new (clean path)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pair-programmer → tdd-guide → code-reviewer → api-guardian (if API)
|
|
39
|
+
→ security-reviewer (if sensitive)
|
|
40
|
+
→ ux-reviewer (if UI)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`pair-programmer` agrees on approach. `tdd-guide` writes failing tests first. The implementer writes code. `code-reviewer` checks it. Specialist reviewers check their domain.
|
|
44
|
+
|
|
45
|
+
### Bug in production
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
debug-detective → tdd-guide → refactor-cleaner (optional)
|
|
49
|
+
(root cause) (regression test)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Always add a regression test after a real-bug fix. Always.
|
|
53
|
+
|
|
54
|
+
### Refactor a module
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
code-explorer → refactor-cleaner → tdd-guide → code-reviewer
|
|
58
|
+
(map dependencies) (cleanup) (verify behavior)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Performance investigation
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
performance-optimizer → tdd-guide → code-reviewer
|
|
65
|
+
(perf regression test)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### API change
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
api-guardian → pair-programmer → tdd-guide → doc-updater
|
|
72
|
+
(blast radius) (impl approach) (changelog)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Security audit
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
security-reviewer → tdd-guide → release-captain
|
|
79
|
+
(security regression test) (patch release)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Open-source a private project
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
opensource-forker → opensource-sanitizer → opensource-packager → release-captain
|
|
86
|
+
(make a clean fork) (strip secrets/PII) (README, license, examples) (cut v0.1.0)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Framework migration
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
migration-guide → pair-programmer → tdd-guide → pr-test-analyzer
|
|
93
|
+
(phase plan) (per-phase impl) (verify coverage on PR)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Build is broken
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
build-error-resolver → dependency-doctor (if dep issue)
|
|
100
|
+
→ env-debugger (if env issue)
|
|
101
|
+
→ debug-detective (if it's actually a runtime bug surfacing at build)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### CI is flaky
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
flake-hunter → tdd-guide → release-captain (if it gates a release)
|
|
108
|
+
(deterministic test)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Git is on fire
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
git-rescue → release-captain (if a release was midway)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Carry Field — What to Pass Forward
|
|
118
|
+
|
|
119
|
+
The `Carry:` line is the most important. Bad carry breaks the chain.
|
|
120
|
+
|
|
121
|
+
**Good carry:**
|
|
122
|
+
> "Bug is in `processPayment()` line 142 — race between `lockBalance()` and `commitTx()`. The lock returns before the DB transaction is durable. Add a regression test that simulates a 50ms commit delay and asserts no double-spend."
|
|
123
|
+
|
|
124
|
+
**Bad carry:**
|
|
125
|
+
> "There was a bug, please test it"
|
|
126
|
+
|
|
127
|
+
The next agent should be able to start work from `Carry:` alone, without re-reading the whole conversation.
|
|
128
|
+
|
|
129
|
+
## When NOT to Hand Off
|
|
130
|
+
|
|
131
|
+
- The current agent's job isn't actually done. Finish it.
|
|
132
|
+
- The user explicitly said "just do this one thing." Respect it.
|
|
133
|
+
- The next step is **trivial** and a handoff would slow it down (e.g., a one-line change). Just do it.
|
|
134
|
+
- The user is **already in flow** and a handoff context-switch would interrupt them. Wait for a natural pause.
|
|
135
|
+
|
|
136
|
+
## Parallel Handoffs
|
|
137
|
+
|
|
138
|
+
Some problems need multiple agents at once, not in sequence:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
"Building a new payment endpoint" →
|
|
142
|
+
|
|
143
|
+
[PARALLEL]
|
|
144
|
+
├─ api-guardian (contract review)
|
|
145
|
+
├─ security-reviewer (auth, input validation, idempotency)
|
|
146
|
+
└─ pair-programmer (overall approach)
|
|
147
|
+
|
|
148
|
+
[SEQUENTIAL after agreement]
|
|
149
|
+
tdd-guide → implementer → code-reviewer
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Announce the parallel set up front so the user knows what's happening:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
This touches three concerns at once. I'm consulting:
|
|
156
|
+
• api-guardian — for contract design
|
|
157
|
+
• security-reviewer — for auth & validation
|
|
158
|
+
• pair-programmer — for overall structure
|
|
159
|
+
|
|
160
|
+
Then we'll move to tests + implementation.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Handoff Hygiene
|
|
164
|
+
|
|
165
|
+
- **Always** name both agents (from/to)
|
|
166
|
+
- **Always** justify the handoff in one line (why this specialist now?)
|
|
167
|
+
- **Always** package the carry — the next agent should not need to re-investigate
|
|
168
|
+
- **Never** chain more than 4 agents in a single response — that's a sign the task is too big and needs decomposition (use `planner`)
|
|
169
|
+
|
|
170
|
+
## Self-Handoff Rule
|
|
171
|
+
|
|
172
|
+
An agent may **stay in role** for the next step if it's still within its specialty. Don't fake a handoff just because the conversation continues:
|
|
173
|
+
|
|
174
|
+
- `debug-detective` may continue after finding the cause to **explain** the cause — that's still debugging.
|
|
175
|
+
- `code-reviewer` may continue to suggest specific fixes — still review scope.
|
|
176
|
+
- But `code-reviewer` writing the actual fix at scale → hand off to the implementer (or appropriate language reviewer with `code-architect` for blueprint).
|
|
177
|
+
|
|
178
|
+
## The Master Conductor: kodelyth-advisor
|
|
179
|
+
|
|
180
|
+
When in doubt about whom to hand off to, the user can always invoke `kodelyth-advisor`. The advisor doesn't do the work — it picks the right specialist and routes.
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Any agent → kodelyth-advisor (if next step is unclear) → Right specialist
|
|
184
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: intent-routing
|
|
3
|
+
description: >
|
|
4
|
+
How Kodelyth ECC auto-detects user intent and routes to the right
|
|
5
|
+
specialist agent without requiring explicit invocation. Read this when
|
|
6
|
+
you (the AI) want to understand the routing rule, when the user asks
|
|
7
|
+
"how does the toolkit decide which agent to use", or when designing a
|
|
8
|
+
new agent and need to know what trigger patterns it should claim.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Intent Routing — How ECC Picks the Right Agent
|
|
12
|
+
|
|
13
|
+
ECC has **two activation paths** for its 53 specialist agents:
|
|
14
|
+
|
|
15
|
+
1. **Explicit** — user types `use <agent>`, `@agent`, or `invoke <agent>`
|
|
16
|
+
2. **Implicit (intent routing)** — the AI reads the user's message, infers the right specialist, announces the routing, and behaves as that agent
|
|
17
|
+
|
|
18
|
+
Most users never type `use ...`. Intent routing is what makes the toolkit feel **alive** instead of like a directory of files.
|
|
19
|
+
|
|
20
|
+
## The Routing Contract
|
|
21
|
+
|
|
22
|
+
When intent routing fires, the AI MUST do four things in order:
|
|
23
|
+
|
|
24
|
+
1. **Acknowledge the routing** in one short line above the response:
|
|
25
|
+
```
|
|
26
|
+
→ Routing to debug-detective (your error message + frustration matches the bug-tracking signal)
|
|
27
|
+
```
|
|
28
|
+
2. **Behave as that agent** — adopt its persona, methodology, and constraints for the rest of the response
|
|
29
|
+
3. **Suggest the explicit invocation** in one closing line:
|
|
30
|
+
```
|
|
31
|
+
Tip: next time you can type "use debug-detective" to invoke me directly.
|
|
32
|
+
```
|
|
33
|
+
4. **Stay transparent** — never silently route. The user must always know which agent is "speaking".
|
|
34
|
+
|
|
35
|
+
## The Source of Truth
|
|
36
|
+
|
|
37
|
+
The full routing table lives in `rules/common/agent-intent-routing.md`. This skill is a **summary + design guide**.
|
|
38
|
+
|
|
39
|
+
If you (an agent author) want to claim trigger patterns for a new agent, you must:
|
|
40
|
+
|
|
41
|
+
1. Add your patterns to `agent-intent-routing.md`
|
|
42
|
+
2. Pick a priority tier (1 = crisis, 10 = chaining)
|
|
43
|
+
3. Verify your patterns don't collide with an existing higher-priority agent
|
|
44
|
+
4. Add a counter-pattern (when NOT to route here)
|
|
45
|
+
|
|
46
|
+
## Priority Tiers (Why They Matter)
|
|
47
|
+
|
|
48
|
+
When two agents could match, the **higher tier wins**.
|
|
49
|
+
|
|
50
|
+
| Tier | Theme | Example agents |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| 1 | Crisis & emotional state | kodelyth-advisor, pair-programmer |
|
|
53
|
+
| 2 | Active pain (something broken) | debug-detective, build-error-resolver, env-debugger |
|
|
54
|
+
| 3 | Quality & review | code-reviewer, security-reviewer, ux-reviewer, api-guardian |
|
|
55
|
+
| 4 | Performance & scale | performance-optimizer |
|
|
56
|
+
| 5 | Planning & architecture | planner, architect, code-architect, migration-guide |
|
|
57
|
+
| 6 | Testing | tdd-guide, e2e-runner, pr-test-analyzer, flake-hunter |
|
|
58
|
+
| 7 | Code hygiene | refactor-cleaner, code-simplifier, type-design-analyzer |
|
|
59
|
+
| 8 | Documentation | doc-updater, docs-lookup, comment-analyzer |
|
|
60
|
+
| 9 | Specialized | seo-specialist, opensource-*, dependency-doctor, git-rescue, release-captain |
|
|
61
|
+
| 10 | Multi-agent chains | (handoffs between any two agents) |
|
|
62
|
+
|
|
63
|
+
**Why crisis is tier 1:** if a user says "I'm stuck on this bug", we route to `kodelyth-advisor` first (emotional state) rather than `debug-detective` (the technical one). The advisor will then often hand off to `debug-detective` once the user describes the actual bug.
|
|
64
|
+
|
|
65
|
+
## What Counts as a Trigger
|
|
66
|
+
|
|
67
|
+
A trigger is a **regex or keyword pattern in the user's message**. Good triggers:
|
|
68
|
+
|
|
69
|
+
- **Direct verbs**: "review", "debug", "optimize", "migrate"
|
|
70
|
+
- **State words**: "broken", "slow", "stuck", "failing", "vulnerable"
|
|
71
|
+
- **Domain terms**: "JWT", "SQL injection", "WCAG", "TypeScript", "Postgres"
|
|
72
|
+
- **Emotional cues**: "I'm lost", "I've been trying for hours", "this won't work"
|
|
73
|
+
|
|
74
|
+
Bad triggers (avoid):
|
|
75
|
+
|
|
76
|
+
- Single common words like "and", "the", "code" (will match everything)
|
|
77
|
+
- Words that are equally valid for two different agents without any disambiguator
|
|
78
|
+
- Metaphors that are easy to miss ("my code is on fire" — too loose)
|
|
79
|
+
|
|
80
|
+
## Counter-Patterns (When NOT to Route)
|
|
81
|
+
|
|
82
|
+
Always include these in any new agent:
|
|
83
|
+
|
|
84
|
+
- The user **already explicitly invoked** another agent — that wins
|
|
85
|
+
- The message is a **one-line factual question** — answer directly, don't route
|
|
86
|
+
- The message is **purely conversational** ("hi", "thanks") — don't route
|
|
87
|
+
- The user explicitly says **"don't route" or "just answer me"** — respect it
|
|
88
|
+
- The user is in a **defined multi-step workflow** with another agent — don't interrupt
|
|
89
|
+
|
|
90
|
+
## Example: Designing a New Agent's Trigger Section
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
### `dependency-doctor` — npm/pip/cargo/maven dep hell
|
|
94
|
+
|
|
95
|
+
| Signal | Examples |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Install failure | "npm install fails", "cannot resolve", "yarn install error" |
|
|
98
|
+
| Lockfile drift | "package-lock conflict", "yarn.lock conflict", "lockfile diff" |
|
|
99
|
+
| CVE | "audit shows", "CVE-", "vulnerable dependency" |
|
|
100
|
+
| Version conflict | "ERESOLVE", "peer dep conflict", "conflicting versions" |
|
|
101
|
+
| Bloat | "bundle too big", "node_modules huge", "dep audit" |
|
|
102
|
+
|
|
103
|
+
Counter-signals (do NOT route here):
|
|
104
|
+
- Generic "build failed" without dep mention → `build-error-resolver`
|
|
105
|
+
- Runtime null pointer → `debug-detective`
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Example: Routing Decisions in the Wild
|
|
109
|
+
|
|
110
|
+
| User says | Route to | Why |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| "I'm getting a TypeError on line 42" | `debug-detective` | T2 — specific error |
|
|
113
|
+
| "Should I use React Context or Zustand?" | `pair-programmer` | T1 — pre-implementation question |
|
|
114
|
+
| "Review my login component" | `typescript-reviewer` (if .ts file) or `code-reviewer` | T3 — review request |
|
|
115
|
+
| "I have no idea where to start with this auth migration" | `kodelyth-advisor` | T1 — lost / overwhelmed |
|
|
116
|
+
| "How do I make this faster?" | `performance-optimizer` | T4 — perf |
|
|
117
|
+
| "Is my JWT signing secure?" | `security-reviewer` | T3 — security keyword |
|
|
118
|
+
| "build failed on Vercel" | `build-error-resolver` | T2 — build failure |
|
|
119
|
+
| "Tests pass locally but fail on CI" | `flake-hunter` then `env-debugger` | T2 — flake or env diff |
|
|
120
|
+
| "Migrate Pages Router to App Router" | `migration-guide` | T5 — framework migration |
|
|
121
|
+
| "Add accessibility to this form" | `ux-reviewer` | T3 — a11y |
|
|
122
|
+
| "open source this project" | `opensource-forker` (chain start) | T9 — OSS chain |
|
|
123
|
+
| "I lost my commits after `reset --hard`" | `git-rescue` | T9 — git crisis |
|
|
124
|
+
|
|
125
|
+
## Anti-Patterns to Avoid
|
|
126
|
+
|
|
127
|
+
- **Silent routing**: jumping into `debug-detective` without a "→ Routing to" line. The user thinks the AI just changed personality randomly.
|
|
128
|
+
- **Over-routing**: claiming `code-reviewer` for every code-related message. Reserve it for explicit review intent.
|
|
129
|
+
- **Under-routing**: ignoring obvious signals because the user didn't type the magic word.
|
|
130
|
+
- **Stacking**: routing to 4 agents at once. Pick **one or at most two parallel agents**.
|
|
131
|
+
|
|
132
|
+
## Skill Authors: Add Your Triggers Here
|
|
133
|
+
|
|
134
|
+
When you build a new agent, update `rules/common/agent-intent-routing.md`. The intent rule is **the toolkit's nervous system**. Better intent rules = better routing = better user experience.
|