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,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kodelyth-memory
|
|
3
|
+
description: Local self-learning memory for AI coding sessions. Captures what works, recalls it next time, shapes context for prompt-cache savings. Zero dependencies, zero telemetry, model-agnostic.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kodelyth Memory — Skill
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
- **At session start** when the task touches a domain the user has worked in before (auth, payments, database, deployment, API integration)
|
|
11
|
+
- **When the user says "that worked"** or signals success after struggle — capture the lesson
|
|
12
|
+
- **When the user asks "have I done this before?"** or seems to be repeating past work
|
|
13
|
+
- **When starting a new feature** in a project with existing memory
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────┐ capture ┌─────────────────┐ inject ┌─────────────────┐
|
|
19
|
+
│ Past session │ ─────────────→│ ~/.kodelyth/ │─────────────→│ Next session │
|
|
20
|
+
│ (you solved X) │ │ memory/ │ │ (X comes up) │
|
|
21
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
22
|
+
│
|
|
23
|
+
│ BM25 keyword + tag retrieval
|
|
24
|
+
│ No embeddings, no network
|
|
25
|
+
↓
|
|
26
|
+
Cache-friendly context block
|
|
27
|
+
(stable prefix → cheap re-reads)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Storage layout
|
|
31
|
+
|
|
32
|
+
All under `~/.kodelyth/memory/` (override with `KODELYTH_MEMORY_DIR`):
|
|
33
|
+
|
|
34
|
+
| File | Purpose |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `memories.jsonl` | Append-only log — every captured memory |
|
|
37
|
+
| `index.json` | Inverted BM25 index for fast retrieval |
|
|
38
|
+
| `patterns.json` | User-level recurring patterns (auto-derived) |
|
|
39
|
+
| `projects/<hash>.json` | Per-project shortcut indexes |
|
|
40
|
+
|
|
41
|
+
## Why BM25 instead of embeddings
|
|
42
|
+
|
|
43
|
+
| Embeddings (OpenAI/local) | BM25 (what we use) |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Semantic match — finds related ideas with no shared words | Keyword + tag match |
|
|
46
|
+
| Requires either network calls or 50MB+ local model | Pure JS, ~3KB |
|
|
47
|
+
| Adds 200-2000ms latency per query | Sub-millisecond |
|
|
48
|
+
| Cost per session | Free forever |
|
|
49
|
+
| Privacy: leaks query text to provider | Stays local |
|
|
50
|
+
|
|
51
|
+
For coding memory, the things you want to recall **almost always share vocabulary** with the trigger — file paths, library names, error strings, framework terms. BM25 nails this. We deliberately chose worse semantic match for vastly better latency, privacy, and cost.
|
|
52
|
+
|
|
53
|
+
## CLI cheatsheet
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Add a memory manually
|
|
57
|
+
node scripts/memory/cli.js remember "Stripe webhook signature failed in production" \
|
|
58
|
+
--approach "Switched body parser from json to raw, validated with constructEvent" \
|
|
59
|
+
--tags payments,stripe,webhooks \
|
|
60
|
+
--language typescript
|
|
61
|
+
|
|
62
|
+
# Search
|
|
63
|
+
node scripts/memory/cli.js search "stripe webhook"
|
|
64
|
+
|
|
65
|
+
# Show what would be injected at session start
|
|
66
|
+
node scripts/memory/cli.js inject --query "add stripe payments"
|
|
67
|
+
|
|
68
|
+
# Extract memory candidates from a Claude Code session log
|
|
69
|
+
node scripts/memory/cli.js extract ~/.claude/projects/<project>/<session>.jsonl
|
|
70
|
+
|
|
71
|
+
# List all memories
|
|
72
|
+
node scripts/memory/cli.js list
|
|
73
|
+
|
|
74
|
+
# Storage stats
|
|
75
|
+
node scripts/memory/cli.js stats
|
|
76
|
+
|
|
77
|
+
# Forget one
|
|
78
|
+
node scripts/memory/cli.js forget <id>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Slash command
|
|
82
|
+
|
|
83
|
+
In Claude Code:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
/memory # Show stats and recent memories
|
|
87
|
+
/memory recall <query> # Search and surface matches
|
|
88
|
+
/memory remember <title> # Capture a new memory (interactive)
|
|
89
|
+
/memory forget <id> # Delete one
|
|
90
|
+
/memory review-session # Extract candidates from current session
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Cache-friendly injection
|
|
94
|
+
|
|
95
|
+
The injected context block is structured for prompt cache reuse:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
[STABLE PREFIX — cached after first call, ~10% cost on subsequent calls]
|
|
99
|
+
## Your recurring patterns (built from N sessions)
|
|
100
|
+
## Recent solutions in this project
|
|
101
|
+
## Detected stack: typescript, next, postgres
|
|
102
|
+
|
|
103
|
+
[VARIABLE SUFFIX — varies per query]
|
|
104
|
+
## Relevant to your current task: "<query>"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For Anthropic models the cache TTL is 5 minutes — typing back-to-back during a coding session keeps the prefix warm. For OpenAI models the prefix is automatically cached when ≥1024 tokens. Other models (Gemini, Llama, Mistral) do not currently cache, so for them the benefit is purely the recall quality, not cost reduction.
|
|
108
|
+
|
|
109
|
+
## Honest limits
|
|
110
|
+
|
|
111
|
+
- **Not "the model learns"** — the model is unchanged. We're just feeding it better context.
|
|
112
|
+
- **Per-machine by default** — sync via Dropbox/iCloud/git on `~/.kodelyth/memory/` if needed.
|
|
113
|
+
- **Cloud-AI platforms** (Windsurf, Antigravity, partial Cursor) — session data is server-side. Auto-extract from past sessions doesn't work there. Manual `/memory remember` still does.
|
|
114
|
+
- **Privacy** — every byte stays on your disk. Verify with `ls -la ~/.kodelyth/memory/`.
|
|
115
|
+
|
|
116
|
+
## Anti-patterns
|
|
117
|
+
|
|
118
|
+
| Don't | Do |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Auto-capture every conversation | Capture only when user signals success |
|
|
121
|
+
| Inject all memories on every session | Inject relevant + recent + patterns only |
|
|
122
|
+
| Capture without showing user the draft | Always confirm before storing |
|
|
123
|
+
| Recall the same memory twice in one session | Track surfaced memories per session |
|
|
124
|
+
| Treat memory as ground truth | Memory is a hint — current task may differ |
|
|
125
|
+
|
|
126
|
+
## Files in this skill
|
|
127
|
+
|
|
128
|
+
- `agents/kodelyth-memory.md` — the agent persona and protocols
|
|
129
|
+
- `scripts/memory/store.js` — storage + BM25 retrieval
|
|
130
|
+
- `scripts/memory/inject.js` — cache-friendly context block builder
|
|
131
|
+
- `scripts/memory/extract.js` — heuristic learning extractor for session logs
|
|
132
|
+
- `scripts/memory/cli.js` — command-line entry point
|
|
133
|
+
- `hooks/memory/capture-stop.js` — Stop hook that runs extractor on session end
|
|
134
|
+
- `hooks/memory/inject-start.js` — SessionStart hook that runs `inject`
|
|
135
|
+
- `commands/memory.md` — `/memory` slash command
|
|
136
|
+
- `rules/common/memory-protocol.md` — when AI should query memory mid-session
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Tests for scripts/memory/store.js — runs against a temp directory
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const test = require('node:test');
|
|
5
|
+
const assert = require('node:assert/strict');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-mem-'));
|
|
11
|
+
process.env.KODELYTH_MEMORY_DIR = TMP;
|
|
12
|
+
|
|
13
|
+
// Require AFTER setting env so the store picks up the temp dir
|
|
14
|
+
const store = require('../../scripts/memory/store');
|
|
15
|
+
const { buildContextBlock } = require('../../scripts/memory/inject');
|
|
16
|
+
|
|
17
|
+
test('tokenise removes stopwords and short tokens', () => {
|
|
18
|
+
const tokens = store.tokenise('The Stripe webhook signature failed in production');
|
|
19
|
+
assert.ok(tokens.includes('stripe'));
|
|
20
|
+
assert.ok(tokens.includes('webhook'));
|
|
21
|
+
assert.ok(tokens.includes('signature'));
|
|
22
|
+
assert.ok(tokens.includes('failed'));
|
|
23
|
+
assert.ok(!tokens.includes('the'));
|
|
24
|
+
assert.ok(!tokens.includes('in'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('capture stores a memory and returns it with id', () => {
|
|
28
|
+
const m = store.capture({
|
|
29
|
+
problem: 'Stripe webhook signature failed in production',
|
|
30
|
+
approach: 'Switched body parser from json to raw, validated with constructEvent',
|
|
31
|
+
tags: ['payments', 'stripe', 'webhooks'],
|
|
32
|
+
project: '/test/project-a',
|
|
33
|
+
language: 'typescript',
|
|
34
|
+
});
|
|
35
|
+
assert.ok(m.id);
|
|
36
|
+
assert.equal(m.problem, 'Stripe webhook signature failed in production');
|
|
37
|
+
assert.equal(m.tags.length, 3);
|
|
38
|
+
assert.ok(m.captured_at);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('capture rejects missing problem or approach', () => {
|
|
42
|
+
assert.throws(() => store.capture({ problem: '', approach: 'x' }));
|
|
43
|
+
assert.throws(() => store.capture({ problem: 'x', approach: '' }));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('recall finds memory by keyword', () => {
|
|
47
|
+
const results = store.recall('stripe webhook');
|
|
48
|
+
assert.ok(results.length >= 1);
|
|
49
|
+
assert.match(results[0].problem, /stripe/i);
|
|
50
|
+
assert.ok(results[0].score > 0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('recall returns empty for irrelevant query', () => {
|
|
54
|
+
const results = store.recall('completely unrelated quantum mechanics');
|
|
55
|
+
assert.equal(results.length, 0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('recallForProject prioritises project memories then falls back to global', () => {
|
|
59
|
+
store.capture({
|
|
60
|
+
problem: 'Database connection pool exhausted',
|
|
61
|
+
approach: 'Increased pool size and added connection timeout',
|
|
62
|
+
tags: ['database', 'postgres'],
|
|
63
|
+
project: '/test/project-b',
|
|
64
|
+
language: 'typescript',
|
|
65
|
+
});
|
|
66
|
+
const projectA = store.recallForProject('/test/project-a', 'webhook');
|
|
67
|
+
assert.ok(projectA.length >= 1);
|
|
68
|
+
assert.equal(projectA[0].project_path, '/test/project-a');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('listAll returns all non-deleted memories', () => {
|
|
72
|
+
const all = store.listAll();
|
|
73
|
+
assert.ok(all.length >= 2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('forget marks memory deleted', () => {
|
|
77
|
+
const all = store.listAll();
|
|
78
|
+
const target = all[0];
|
|
79
|
+
const ok = store.forget(target.id);
|
|
80
|
+
assert.equal(ok, true);
|
|
81
|
+
const after = store.listAll();
|
|
82
|
+
assert.ok(after.length < all.length);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('rebuildIndex restores searchability after manual log edit', () => {
|
|
86
|
+
const r = store.rebuildIndex();
|
|
87
|
+
assert.ok(r.count >= 1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('stats summarises store contents', () => {
|
|
91
|
+
const s = store.stats();
|
|
92
|
+
assert.ok(s.total >= 1);
|
|
93
|
+
assert.equal(s.storageDir, TMP);
|
|
94
|
+
assert.ok(s.byLanguage.typescript >= 1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('buildContextBlock returns null when memory is empty', () => {
|
|
98
|
+
const emptyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-empty-'));
|
|
99
|
+
process.env.KODELYTH_MEMORY_DIR = emptyDir;
|
|
100
|
+
// Force fresh require by clearing cache
|
|
101
|
+
delete require.cache[require.resolve('../../scripts/memory/store')];
|
|
102
|
+
delete require.cache[require.resolve('../../scripts/memory/inject')];
|
|
103
|
+
const { buildContextBlock: bcb } = require('../../scripts/memory/inject');
|
|
104
|
+
const result = bcb({ projectRoot: '/test/project-x' });
|
|
105
|
+
assert.equal(result, null);
|
|
106
|
+
// Restore
|
|
107
|
+
process.env.KODELYTH_MEMORY_DIR = TMP;
|
|
108
|
+
delete require.cache[require.resolve('../../scripts/memory/store')];
|
|
109
|
+
delete require.cache[require.resolve('../../scripts/memory/inject')];
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('buildContextBlock returns structured block when memory exists', () => {
|
|
113
|
+
const fresh = require('../../scripts/memory/inject');
|
|
114
|
+
const result = fresh.buildContextBlock({
|
|
115
|
+
projectRoot: '/test/project-b',
|
|
116
|
+
query: 'database pool',
|
|
117
|
+
});
|
|
118
|
+
assert.ok(result);
|
|
119
|
+
assert.ok(result.text.includes('Kodelyth Memory'));
|
|
120
|
+
assert.ok(result.memoryCount >= 1);
|
|
121
|
+
});
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
// Kodelyth Lens — Agent Auto-Tracker
|
|
2
|
-
// Detects which ECC agents were used from conversation messages
|
|
3
|
-
// Works across Claude Code, Cursor, Windsurf, Codex session logs
|
|
4
|
-
// Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
|
|
5
|
-
|
|
6
|
-
export const ECC_AGENTS = [
|
|
7
|
-
// Kodelyth Exclusive
|
|
8
|
-
'kodelyth-advisor', 'debug-detective', 'ux-reviewer', 'api-guardian',
|
|
9
|
-
'pair-programmer', 'migration-guide',
|
|
10
|
-
// Code Review
|
|
11
|
-
'code-reviewer', 'typescript-reviewer', 'python-reviewer', 'go-reviewer',
|
|
12
|
-
'rust-reviewer', 'java-reviewer', 'kotlin-reviewer', 'cpp-reviewer',
|
|
13
|
-
'csharp-reviewer', 'flutter-reviewer',
|
|
14
|
-
// Build Fixers
|
|
15
|
-
'build-error-resolver', 'go-build-resolver', 'rust-build-resolver',
|
|
16
|
-
'java-build-resolver', 'kotlin-build-resolver', 'cpp-build-resolver',
|
|
17
|
-
'dart-build-resolver', 'pytorch-build-resolver',
|
|
18
|
-
// Planning
|
|
19
|
-
'planner', 'architect', 'code-architect', 'code-explorer', 'chief-of-staff',
|
|
20
|
-
// Security
|
|
21
|
-
'security-reviewer', 'healthcare-reviewer',
|
|
22
|
-
// Quality
|
|
23
|
-
'refactor-cleaner', 'code-simplifier', 'performance-optimizer',
|
|
24
|
-
'type-design-analyzer', 'silent-failure-hunter',
|
|
25
|
-
// Testing
|
|
26
|
-
'tdd-guide', 'e2e-runner', 'pr-test-analyzer',
|
|
27
|
-
// Documentation
|
|
28
|
-
'doc-updater', 'docs-lookup', 'comment-analyzer',
|
|
29
|
-
// Open Source
|
|
30
|
-
'opensource-forker', 'opensource-sanitizer', 'opensource-packager',
|
|
31
|
-
// Specialized
|
|
32
|
-
'seo-specialist', 'database-reviewer', 'loop-operator', 'harness-optimizer',
|
|
33
|
-
// GAN
|
|
34
|
-
'gan-planner', 'gan-generator', 'gan-evaluator',
|
|
35
|
-
// Meta
|
|
36
|
-
'claude-code-guide', 'conversation-analyzer',
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
// Agent category colors for dashboard
|
|
40
|
-
export const AGENT_COLORS = {
|
|
41
|
-
'kodelyth-advisor': '#7c3aed',
|
|
42
|
-
'debug-detective': '#dc2626',
|
|
43
|
-
'ux-reviewer': '#0891b2',
|
|
44
|
-
'api-guardian': '#f59e0b',
|
|
45
|
-
'pair-programmer': '#10b981',
|
|
46
|
-
'migration-guide': '#8b5cf6',
|
|
47
|
-
'code-reviewer': '#3b82f6',
|
|
48
|
-
'security-reviewer': '#ef4444',
|
|
49
|
-
'tdd-guide': '#06b6d4',
|
|
50
|
-
'performance-optimizer': '#f97316',
|
|
51
|
-
'planner': '#6366f1',
|
|
52
|
-
'architect': '#8b5cf6',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const DEFAULT_COLOR = '#475569';
|
|
56
|
-
|
|
57
|
-
export function getAgentColor(name) {
|
|
58
|
-
return AGENT_COLORS[name] || DEFAULT_COLOR;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── Intent / Emotion Detection ────────────────────────────────────────────────
|
|
62
|
-
// Maps user emotional state and task keywords → recommended ECC agents
|
|
63
|
-
// This layer runs in ADDITION to explicit invocation patterns —
|
|
64
|
-
// it infers agent usage from what the user actually needed, not just what they typed.
|
|
65
|
-
const INTENT_MAP = [
|
|
66
|
-
// ── Stuck / Lost / Don't know where to start ─────────────────────────────
|
|
67
|
-
{
|
|
68
|
-
patterns: [
|
|
69
|
-
/i[' ]?m\s+(stuck|lost|confused|overwhelmed|not sure|unsure|clueless)/i,
|
|
70
|
-
/don[' ]?t\s+know\s+(where|how|what)\s+to\s+start/i,
|
|
71
|
-
/where\s+do\s+i\s+(even\s+)?start/i,
|
|
72
|
-
/help\s+me\s+figure\s+out/i,
|
|
73
|
-
/what\s+(should|do)\s+i\s+do/i,
|
|
74
|
-
/i\s+don[' ]?t\s+understand\s+this/i,
|
|
75
|
-
/not\s+sure\s+what\s+to\s+do\s+next/i,
|
|
76
|
-
],
|
|
77
|
-
agents: ['kodelyth-advisor'],
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
// ── Bug / Error / Something broke ────────────────────────────────────────
|
|
81
|
-
{
|
|
82
|
-
patterns: [
|
|
83
|
-
/\b(bug|bugs|buggy)\b/i,
|
|
84
|
-
/\b(broken|broke|breaking)\b/i,
|
|
85
|
-
/\berror\b.*\b(can[' ]?t|cannot|don[' ]?t|wont|won[' ]?t)\s+fix/i,
|
|
86
|
-
/why\s+(is|does|isn[' ]?t|doesn[' ]?t)\s+this\s+(work|working|fail|failing)/i,
|
|
87
|
-
/\b(crash|crashed|crashing|exception|traceback|stack\s*trace)\b/i,
|
|
88
|
-
/i[' ]?ve\s+been\s+(trying|debugging|fighting)\s+this\s+(for|all)/i,
|
|
89
|
-
/can[' ]?t\s+figure\s+out\s+(what|why|where|how)/i,
|
|
90
|
-
/this\s+(code|thing|function|module|component)\s+(won[' ]?t|doesn[' ]?t)\s+work/i,
|
|
91
|
-
/something\s+is\s+(wrong|broken|off|weird)\s+with/i,
|
|
92
|
-
/\bnot\s+working\b/i,
|
|
93
|
-
],
|
|
94
|
-
agents: ['debug-detective'],
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
// ── Build failure ─────────────────────────────────────────────────────────
|
|
98
|
-
{
|
|
99
|
-
patterns: [
|
|
100
|
-
/build\s+(fail|failed|failing|error|broke)/i,
|
|
101
|
-
/\bcompile\s+(error|fail|failed)\b/i,
|
|
102
|
-
/\btype\s+error\b/i,
|
|
103
|
-
/\bts\s*\(\d+\)\b/i,
|
|
104
|
-
/\bcargo\s+build\b/i,
|
|
105
|
-
/\bnpm\s+(run\s+build|build)\b.*\bfail/i,
|
|
106
|
-
/can[' ]?t\s+build/i,
|
|
107
|
-
],
|
|
108
|
-
agents: ['build-error-resolver'],
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
// ── Security / Vulnerability ──────────────────────────────────────────────
|
|
112
|
-
{
|
|
113
|
-
patterns: [
|
|
114
|
-
/\b(security|secure|insecure|vulnerability|vulnerabilities|vulnerable)\b/i,
|
|
115
|
-
/\b(sql\s+injection|xss|csrf|authentication|authorization|auth\s+bypass)\b/i,
|
|
116
|
-
/\b(hack|hacked|exposed|leaked|leak|breach|exploit)\b/i,
|
|
117
|
-
/is\s+this\s+(safe|secure|vulnerable)/i,
|
|
118
|
-
/\b(api\s+key|secret|credential|password)\s+(exposed|leaked|in\s+(code|git|repo))\b/i,
|
|
119
|
-
],
|
|
120
|
-
agents: ['security-reviewer'],
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
// ── Performance / Slow / Bottleneck ──────────────────────────────────────
|
|
124
|
-
{
|
|
125
|
-
patterns: [
|
|
126
|
-
/\b(slow|sluggish|laggy|performance|performant|optimize|optimization)\b/i,
|
|
127
|
-
/\b(memory\s+(leak|usage|pressure)|high\s+cpu|high\s+memory)\b/i,
|
|
128
|
-
/\b(bottleneck|profiling|bundle\s+size|load\s+time|render\s+time)\b/i,
|
|
129
|
-
/why\s+is\s+this\s+(so\s+)?slow/i,
|
|
130
|
-
/make\s+this\s+(faster|more\s+efficient|quicker)/i,
|
|
131
|
-
/\b(n\+1|n\s*\+\s*1|query\s+optimization)\b/i,
|
|
132
|
-
],
|
|
133
|
-
agents: ['performance-optimizer'],
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
// ── Planning / Architecture / Design ─────────────────────────────────────
|
|
137
|
-
{
|
|
138
|
-
patterns: [
|
|
139
|
-
/how\s+should\s+i\s+(structure|design|architect|build|implement)\s+this/i,
|
|
140
|
-
/\b(plan|planning|roadmap|approach|strategy)\b.*\b(feature|system|project|app)\b/i,
|
|
141
|
-
/\b(architecture|architectural|system\s+design)\b/i,
|
|
142
|
-
/where\s+should\s+(this|the\s+code|it)\s+go/i,
|
|
143
|
-
/best\s+(way|approach|practice)\s+to\s+(build|implement|design)/i,
|
|
144
|
-
/should\s+i\s+(use|create|add|build)\s+a/i,
|
|
145
|
-
],
|
|
146
|
-
agents: ['planner', 'architect'],
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
// ── Code review / Quality ─────────────────────────────────────────────────
|
|
150
|
-
{
|
|
151
|
-
patterns: [
|
|
152
|
-
/\b(review|code\s+review|review\s+this\s+code|lgtm|looks\s+good)\b/i,
|
|
153
|
-
/\b(clean\s+(up|code)|refactor|dead\s+code|unused|duplicate)\b/i,
|
|
154
|
-
/is\s+this\s+(code\s+)?(good|clean|correct|right|okay|ok)\??/i,
|
|
155
|
-
/anything\s+(wrong|bad|to\s+improve)\s+(with|in)\s+this/i,
|
|
156
|
-
],
|
|
157
|
-
agents: ['code-reviewer'],
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
// ── Tests / Testing / Coverage ────────────────────────────────────────────
|
|
161
|
-
{
|
|
162
|
-
patterns: [
|
|
163
|
-
/\b(test|tests|testing|unit\s+test|integration\s+test|e2e)\b/i,
|
|
164
|
-
/\b(coverage|tdd|test\s+driven|failing\s+test)\b/i,
|
|
165
|
-
/write\s+(a\s+)?test\s+for/i,
|
|
166
|
-
/how\s+do\s+i\s+test\s+this/i,
|
|
167
|
-
],
|
|
168
|
-
agents: ['tdd-guide'],
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
// ── UI / UX / Frontend / Accessibility ───────────────────────────────────
|
|
172
|
-
{
|
|
173
|
-
patterns: [
|
|
174
|
-
/\b(ui|ux|user\s+(interface|experience)|frontend|front[- ]end)\b/i,
|
|
175
|
-
/\b(accessibility|a11y|wcag|screen\s+reader|aria)\b/i,
|
|
176
|
-
/\b(usability|interaction|click|hover|form|button|modal)\b/i,
|
|
177
|
-
/does\s+this\s+(look|feel|work)\s+(right|good|okay)\s+for\s+(users|mobile)/i,
|
|
178
|
-
],
|
|
179
|
-
agents: ['ux-reviewer'],
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
// ── Documentation ─────────────────────────────────────────────────────────
|
|
183
|
-
{
|
|
184
|
-
patterns: [
|
|
185
|
-
/\b(document|docs|documentation|readme|jsdoc|comment)\b/i,
|
|
186
|
-
/add\s+(docs|documentation|comments|jsdoc)/i,
|
|
187
|
-
/how\s+do\s+i\s+document/i,
|
|
188
|
-
],
|
|
189
|
-
agents: ['doc-updater'],
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
// ── Database / SQL / Query ────────────────────────────────────────────────
|
|
193
|
-
{
|
|
194
|
-
patterns: [
|
|
195
|
-
/\b(sql|database|db|query|migration|schema|index|postgres|mysql|supabase)\b/i,
|
|
196
|
-
/\b(slow\s+query|n\+1|missing\s+index|table\s+scan)\b/i,
|
|
197
|
-
/how\s+should\s+i\s+(model|structure|design)\s+(the\s+)?(database|schema|table)/i,
|
|
198
|
-
],
|
|
199
|
-
agents: ['database-reviewer'],
|
|
200
|
-
},
|
|
201
|
-
|
|
202
|
-
// ── Open source / Release ─────────────────────────────────────────────────
|
|
203
|
-
{
|
|
204
|
-
patterns: [
|
|
205
|
-
/\b(open[\s-]?source|publish|release|public\s+repo|make\s+(it\s+)?public)\b/i,
|
|
206
|
-
/\b(sanitize|strip\s+secrets|remove\s+(credentials|api\s+keys))\b/i,
|
|
207
|
-
],
|
|
208
|
-
agents: ['opensource-forker', 'opensource-sanitizer'],
|
|
209
|
-
},
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Detect ECC agents from user intent/emotion — goes beyond explicit invocations.
|
|
214
|
-
* Maps what the user says they're experiencing → the agent that helps with that.
|
|
215
|
-
* @param {Array<{role, content, timestamp}>} messages
|
|
216
|
-
* @returns {Array<{agent, timestamp, role, source}>}
|
|
217
|
-
*/
|
|
218
|
-
export function detectAgentsByIntent(messages) {
|
|
219
|
-
const found = [];
|
|
220
|
-
|
|
221
|
-
for (const msg of messages) {
|
|
222
|
-
if (!msg.content) continue;
|
|
223
|
-
// Only scan user messages for intent signals
|
|
224
|
-
if (msg.role && msg.role !== 'user') continue;
|
|
225
|
-
|
|
226
|
-
const text = typeof msg.content === 'string'
|
|
227
|
-
? msg.content
|
|
228
|
-
: JSON.stringify(msg.content);
|
|
229
|
-
|
|
230
|
-
for (const { patterns, agents } of INTENT_MAP) {
|
|
231
|
-
const matched = patterns.some(p => p.test(text));
|
|
232
|
-
if (!matched) continue;
|
|
233
|
-
|
|
234
|
-
for (const agent of agents) {
|
|
235
|
-
found.push({
|
|
236
|
-
agent,
|
|
237
|
-
timestamp: msg.timestamp || null,
|
|
238
|
-
role: 'user',
|
|
239
|
-
source: 'intent',
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Deduplicate by agent+timestamp
|
|
246
|
-
const seen = new Set();
|
|
247
|
-
return found.filter(f => {
|
|
248
|
-
const key = `${f.agent}|${f.timestamp}`;
|
|
249
|
-
if (seen.has(key)) return false;
|
|
250
|
-
seen.add(key);
|
|
251
|
-
return true;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Patterns that indicate ECC agent invocation
|
|
256
|
-
const INVOKE_PATTERNS = [
|
|
257
|
-
/\buse\s+([\w-]+)/gi,
|
|
258
|
-
/\brun\s+([\w-]+)\s+agent/gi,
|
|
259
|
-
/@([\w-]+)/g,
|
|
260
|
-
/invoke\s+([\w-]+)/gi,
|
|
261
|
-
/delegate\s+to\s+([\w-]+)/gi,
|
|
262
|
-
/ask\s+([\w-]+)\s+to/gi,
|
|
263
|
-
/launch\s+([\w-]+)/gi,
|
|
264
|
-
];
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Detect ECC agent names in a list of messages.
|
|
268
|
-
* Combines two detection layers:
|
|
269
|
-
* 1. Explicit invocation — "use debug-detective", "@code-reviewer", etc.
|
|
270
|
-
* 2. Intent / emotion — "I'm stuck on this bug" → debug-detective
|
|
271
|
-
* @param {Array<{role, content, timestamp}>} messages
|
|
272
|
-
* @returns {Array<{agent, timestamp, role, source}>}
|
|
273
|
-
*/
|
|
274
|
-
export function detectAgentsInMessages(messages) {
|
|
275
|
-
const explicit = detectAgentsExplicit(messages);
|
|
276
|
-
const intent = detectAgentsByIntent(messages);
|
|
277
|
-
|
|
278
|
-
// Merge — prefer explicit over intent for the same agent+timestamp
|
|
279
|
-
const seen = new Set();
|
|
280
|
-
const merged = [];
|
|
281
|
-
|
|
282
|
-
for (const f of [...explicit, ...intent]) {
|
|
283
|
-
const key = `${f.agent}|${f.timestamp}`;
|
|
284
|
-
if (seen.has(key)) continue;
|
|
285
|
-
seen.add(key);
|
|
286
|
-
merged.push(f);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return merged;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Detect ECC agents via explicit invocation patterns only.
|
|
294
|
-
* @param {Array<{role, content, timestamp}>} messages
|
|
295
|
-
* @returns {Array<{agent, timestamp, role, source}>}
|
|
296
|
-
*/
|
|
297
|
-
function detectAgentsExplicit(messages) {
|
|
298
|
-
const found = [];
|
|
299
|
-
|
|
300
|
-
for (const msg of messages) {
|
|
301
|
-
if (!msg.content) continue;
|
|
302
|
-
const text = typeof msg.content === 'string'
|
|
303
|
-
? msg.content
|
|
304
|
-
: JSON.stringify(msg.content);
|
|
305
|
-
|
|
306
|
-
for (const pattern of INVOKE_PATTERNS) {
|
|
307
|
-
pattern.lastIndex = 0;
|
|
308
|
-
let match;
|
|
309
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
310
|
-
const candidate = match[1].toLowerCase().trim();
|
|
311
|
-
if (ECC_AGENTS.includes(candidate)) {
|
|
312
|
-
found.push({
|
|
313
|
-
agent: candidate,
|
|
314
|
-
timestamp: msg.timestamp || null,
|
|
315
|
-
role: msg.role || 'user',
|
|
316
|
-
source: 'explicit',
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Deduplicate by agent+timestamp
|
|
324
|
-
const seen = new Set();
|
|
325
|
-
return found.filter(f => {
|
|
326
|
-
const key = `${f.agent}|${f.timestamp}`;
|
|
327
|
-
if (seen.has(key)) return false;
|
|
328
|
-
seen.add(key);
|
|
329
|
-
return true;
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Aggregate agent usage across all sessions into leaderboard
|
|
335
|
-
* @param {Array} sessions
|
|
336
|
-
* @returns {Array<AgentStat>}
|
|
337
|
-
*/
|
|
338
|
-
export function buildAgentLeaderboard(sessions) {
|
|
339
|
-
const map = {};
|
|
340
|
-
|
|
341
|
-
for (const session of sessions) {
|
|
342
|
-
for (const use of (session.agents || [])) {
|
|
343
|
-
const { agent } = use;
|
|
344
|
-
if (!map[agent]) {
|
|
345
|
-
map[agent] = {
|
|
346
|
-
name: agent,
|
|
347
|
-
color: getAgentColor(agent),
|
|
348
|
-
calls: 0,
|
|
349
|
-
sessionIds: new Set(),
|
|
350
|
-
platforms: new Set(),
|
|
351
|
-
lastUsed: null,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
map[agent].calls++;
|
|
355
|
-
map[agent].sessionIds.add(session.id || session.timestamp || Math.random());
|
|
356
|
-
map[agent].platforms.add(session.platform || 'unknown');
|
|
357
|
-
if (!map[agent].lastUsed || (use.timestamp && use.timestamp > map[agent].lastUsed)) {
|
|
358
|
-
map[agent].lastUsed = use.timestamp;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return Object.values(map)
|
|
364
|
-
.map(a => ({ ...a, sessions: a.sessionIds.size, platforms: [...a.platforms] }))
|
|
365
|
-
.sort((a, b) => b.calls - a.calls);
|
|
366
|
-
}
|