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.
Files changed (46) hide show
  1. package/AGENTS.md +101 -181
  2. package/CHANGELOG.md +67 -0
  3. package/CLAUDE.md +72 -63
  4. package/KODELYTH.md +79 -44
  5. package/README.md +244 -192
  6. package/VERSION +1 -1
  7. package/agents/dependency-doctor.md +120 -0
  8. package/agents/env-debugger.md +154 -0
  9. package/agents/flake-hunter.md +142 -0
  10. package/agents/git-rescue.md +133 -0
  11. package/agents/kodelyth-memory.md +87 -0
  12. package/agents/release-captain.md +190 -0
  13. package/bin/kodelyth-ecc.js +18 -12
  14. package/commands/memory.md +62 -0
  15. package/hooks/hooks.json +26 -0
  16. package/hooks/memory/capture-stop.js +88 -0
  17. package/hooks/memory/inject-start.js +60 -0
  18. package/install.ps1 +28 -9
  19. package/install.sh +11 -97
  20. package/package.json +4 -2
  21. package/rules/common/agent-intent-routing.md +337 -0
  22. package/rules/common/memory-protocol.md +56 -0
  23. package/scripts/memory/cli.js +200 -0
  24. package/scripts/memory/extract.js +176 -0
  25. package/scripts/memory/inject.js +145 -0
  26. package/scripts/memory/store.js +300 -0
  27. package/skills/agent-handoff/SKILL.md +184 -0
  28. package/skills/intent-routing/SKILL.md +134 -0
  29. package/skills/kodelyth-memory/SKILL.md +136 -0
  30. package/tests/memory/store.test.js +121 -0
  31. package/dashboard/lib/agent-tracker.js +0 -366
  32. package/dashboard/lib/aggregator.js +0 -119
  33. package/dashboard/lib/cost-calculator.js +0 -50
  34. package/dashboard/lib/platform-detector.js +0 -89
  35. package/dashboard/lib/readers/antigravity-reader.js +0 -113
  36. package/dashboard/lib/readers/claude-reader.js +0 -135
  37. package/dashboard/lib/readers/codex-reader.js +0 -192
  38. package/dashboard/lib/readers/cursor-reader.js +0 -135
  39. package/dashboard/lib/readers/opencode-reader.js +0 -201
  40. package/dashboard/lib/readers/windsurf-reader.js +0 -146
  41. package/dashboard/package.json +0 -24
  42. package/dashboard/public/index.html +0 -1221
  43. package/dashboard/server.js +0 -119
  44. package/scripts/agent-tracker-hook.js +0 -81
  45. package/social/readme-lens.svg +0 -140
  46. 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.