kodelyth-ecc 1.7.3 → 1.7.5
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/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +41 -0
- package/.github/ISSUE_TEMPLATE/good_first_issue.md +36 -0
- package/CHANGELOG.md +43 -0
- package/GITHUB_SETUP.md +553 -0
- package/README.md +55 -6
- package/VERSION +1 -1
- package/actions/ecc-review/README.md +7 -7
- package/brand/concepts.svg +109 -0
- package/brand/convert.js +161 -0
- package/brand/favicon.svg +7 -0
- package/brand/fb-cover.svg +34 -0
- package/brand/fb-profile.svg +21 -0
- package/brand/kodelyth-dark.svg +24 -0
- package/brand/kodelyth-light.svg +24 -0
- package/brand/kodelyth-mark.svg +20 -0
- package/brand/package.json +12 -0
- package/bundles/enterprise.md +1 -1
- package/bundles/indie-hacker.md +1 -1
- package/bundles/red-team.md +1 -1
- package/docs/supply-chain.md +1 -1
- package/package.json +4 -1
- package/rules/common/memory-protocol.md +35 -4
- package/scripts/dashboard/data.js +261 -4
- package/scripts/dashboard/server.js +28 -3
- package/scripts/dashboard/static/index.html +63 -3
- package/social/card-agents.svg +12 -0
- package/social/card-install.svg +13 -1
- package/social/card-main.svg +14 -2
- package/social/facebook-v150.svg +17 -4
- package/social/github-social-preview.svg +51 -138
- package/social/hype-compound-learning.svg +129 -0
- package/social/hype-devil-mode.svg +240 -0
- package/social/hype-mcp-server.svg +148 -0
- package/social/hype-parallel-agents.svg +162 -0
- package/social/hype-stats-hero.svg +138 -0
- package/social/og-image.svg +65 -0
- package/social/readme-agents.svg +14 -2
- package/social/readme-hero.svg +17 -4
- package/social/section-agents.svg +12 -0
- package/social/section-author.svg +12 -0
- package/social/section-dashboard.svg +12 -0
- package/social/section-devil.svg +12 -0
- package/social/section-hooks.svg +12 -0
- package/social/section-install.svg +12 -0
- package/social/section-learning.svg +12 -0
- package/social/section-mcp.svg +13 -1
- package/social/section-memory.svg +12 -0
- package/social/section-parallel.svg +12 -0
- package/social/section-routing.svg +12 -0
- package/social/twitter-threads.md +621 -0
- package/social/x-card-agents-grid.svg +14 -2
- package/social/x-card-free.svg +14 -2
- package/social/x-card-hook.svg +17 -5
- package/tests/dashboard/data.test.js +156 -0
- package/tests/dashboard/server.test.js +17 -0
- package/wiki/FAQ.md +1 -1
- package/wiki/Home.md +5 -3
- package/wiki/Installation-Guide.md +2 -2
- package/MARKETING_PLAN.md +0 -893
- package/cat +0 -0
- package/social/facebook-group/POST.md +0 -121
- package/social/facebook-group/fb-1-3am-debug.png +0 -0
- package/social/facebook-group/fb-1-3am-debug.svg +0 -97
- package/social/facebook-group/fb-2-cpu-upgrade.png +0 -0
- package/social/facebook-group/fb-2-cpu-upgrade.svg +0 -132
- package/social/facebook-group/fb-3-before-after.png +0 -0
- package/social/facebook-group/fb-3-before-after.svg +0 -94
- package/videos/README.md +0 -88
- package/videos/video-01-install-and-routing.md +0 -258
- package/videos/video-02-compound-memory.md +0 -301
- package/videos/video-03-devil-mode.md +0 -294
- package/videos/video-04-mcp-server.md +0 -391
|
@@ -6,19 +6,37 @@
|
|
|
6
6
|
|
|
7
7
|
A local file at `~/.kodelyth/memory/memories.jsonl` storing solutions, patterns, and gotchas extracted from past sessions. Retrieval is BM25 (keyword + tag matching). It is **not** a learned model — it is a retrieval store that gives you better context.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Cross-IDE: the same file is read/written by every IDE on this machine.** A memory captured in Claude Code is recall-able from Windsurf, Cursor, Antigravity, Codex, and any other MCP-capable client. There is one shared store.
|
|
10
|
+
|
|
11
|
+
If the SessionStart hook ran (Claude Code only — other IDEs do not have a hook system), you have already received a memory block in your initial context with:
|
|
10
12
|
- The user's recurring patterns (tags seen across multiple sessions)
|
|
11
13
|
- Recent solutions in this project
|
|
12
14
|
- Their detected stack
|
|
13
15
|
|
|
16
|
+
If you are NOT in Claude Code (Windsurf / Cursor / Antigravity / Codex / etc.) you must proactively call the MCP tools below — no hook will surface memories for you automatically.
|
|
17
|
+
|
|
18
|
+
## How to recall memory (works in EVERY IDE)
|
|
19
|
+
|
|
20
|
+
Call the Kodelyth MCP tool **`recall_memory`** with:
|
|
21
|
+
- `query`: the user's task or topic in their own words
|
|
22
|
+
- `project_root` (optional): the absolute path of the current project — scopes recall to that project's memories first
|
|
23
|
+
- `limit` (optional, default 5)
|
|
24
|
+
|
|
25
|
+
Equivalent CLIs (any terminal):
|
|
26
|
+
- `npx kodelyth-ecc mcp-call kodelyth recall_memory --query "<text>"`
|
|
27
|
+
- `node scripts/memory/cli.js search "<query>"` (if running from the repo)
|
|
28
|
+
|
|
14
29
|
## When to recall memory mid-session
|
|
15
30
|
|
|
16
|
-
Trigger
|
|
31
|
+
Trigger `recall_memory` when **any** of these is true:
|
|
17
32
|
|
|
18
|
-
1. The user describes a task in a domain that matches a tag in their memory (`payments`, `auth`, `database`, `deployment`, etc.)
|
|
33
|
+
1. The user describes a task in a domain that likely matches a tag in their memory (`payments`, `auth`, `database`, `deployment`, `tailwind`, `prisma`, etc.)
|
|
19
34
|
2. The user asks "have I done this before?" or "how did I solve X last time?"
|
|
20
|
-
3. The user mentions a library, framework, or service by name
|
|
35
|
+
3. The user mentions a library, framework, or service by name
|
|
21
36
|
4. You're about to commit to an architectural decision and want to check past precedent
|
|
37
|
+
5. **First substantive prompt of a non-Claude-Code session** — always recall once at the start so you don't miss prior context
|
|
38
|
+
|
|
39
|
+
Skip recall on trivial prompts (`ok`, `yes`, `thanks`), agent invocations (`use <agent>`, `@<agent>`), and meta-questions about ECC itself.
|
|
22
40
|
|
|
23
41
|
## How to surface a recalled memory
|
|
24
42
|
|
|
@@ -37,6 +55,19 @@ Capture when **all** are true:
|
|
|
37
55
|
|
|
38
56
|
Show the user the draft memory before storing. Never silently capture.
|
|
39
57
|
|
|
58
|
+
### How to capture (works in EVERY IDE)
|
|
59
|
+
|
|
60
|
+
Call the Kodelyth MCP tool **`capture_memory`** with:
|
|
61
|
+
- `problem`: short statement of what was hard (required)
|
|
62
|
+
- `approach`: the solution that worked, with the key insight (required)
|
|
63
|
+
- `tags`: array of short keywords (`['tailwind', 'css', 'v4']`) — these power recall
|
|
64
|
+
- `language` (optional): primary language of the fix
|
|
65
|
+
- `project_root` (optional): absolute path of the project
|
|
66
|
+
- `files` (optional): files touched
|
|
67
|
+
- `gotchas` (optional): array of pitfalls future-you should avoid
|
|
68
|
+
|
|
69
|
+
Equivalent CLI: `npx kodelyth-ecc mcp-call kodelyth capture_memory --problem "..." --approach "..."`
|
|
70
|
+
|
|
40
71
|
## What you must not do
|
|
41
72
|
|
|
42
73
|
- **Never** auto-recall the same memory twice in one session (the user has seen it)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// scripts/dashboard/data.js
|
|
2
2
|
//
|
|
3
|
-
// Local observability dashboard: pure aggregators (v1.7.
|
|
3
|
+
// Local observability dashboard: pure aggregators (v1.7.5).
|
|
4
4
|
//
|
|
5
5
|
// All data the dashboard renders comes from:
|
|
6
6
|
// - The MCP catalog (filesystem reads of agents/, skills/, commands/, rules/, bundles/)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// - The evolve signal streams (~/.kodelyth/evolve/)
|
|
9
9
|
// - The token-budget hook state (${KODELYTH_TOKEN_BUDGET_DIR})
|
|
10
10
|
// - The orchestration session dirs (.orchestration/<session>/)
|
|
11
|
+
// - Live IDE session activity (Claude Code, Windsurf, Antigravity)
|
|
11
12
|
//
|
|
12
13
|
// Every aggregator is pure: takes its inputs explicitly OR uses well-defined
|
|
13
14
|
// env-overridable defaults. No global mutable state. Errors are swallowed
|
|
@@ -45,6 +46,35 @@ function defaultBudgetDir() {
|
|
|
45
46
|
function defaultCoordRoot() {
|
|
46
47
|
return process.env.KODELYTH_COORDINATION_ROOT || path.join(process.cwd(), '.orchestration');
|
|
47
48
|
}
|
|
49
|
+
function defaultClaudeProjectsDir() {
|
|
50
|
+
return process.env.KODELYTH_CLAUDE_PROJECTS_DIR || path.join(os.homedir(), '.claude', 'projects');
|
|
51
|
+
}
|
|
52
|
+
function defaultWindsurfStateDir() {
|
|
53
|
+
return process.env.KODELYTH_WINDSURF_DIR || path.join(os.homedir(), '.codeium', 'windsurf');
|
|
54
|
+
}
|
|
55
|
+
function defaultWindsurfNextStateDir() {
|
|
56
|
+
// Windsurf Next (newer Codeium release) uses a separate state dir.
|
|
57
|
+
return process.env.KODELYTH_WINDSURF_NEXT_DIR || path.join(os.homedir(), '.codeium', 'windsurf-next');
|
|
58
|
+
}
|
|
59
|
+
function defaultCursorWorkspaceDir() {
|
|
60
|
+
if (process.env.KODELYTH_CURSOR_DIR) return process.env.KODELYTH_CURSOR_DIR;
|
|
61
|
+
const home = os.homedir();
|
|
62
|
+
switch (process.platform) {
|
|
63
|
+
case 'darwin': return path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage');
|
|
64
|
+
case 'win32': return path.join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'workspaceStorage');
|
|
65
|
+
default: return path.join(home, '.config', 'Cursor', 'User', 'workspaceStorage');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function defaultAntigravityStateDir() {
|
|
69
|
+
// Best-effort — Google Antigravity is in early access and the canonical session
|
|
70
|
+
// path is not yet documented. Users can override via KODELYTH_ANTIGRAVITY_DIR.
|
|
71
|
+
return process.env.KODELYTH_ANTIGRAVITY_DIR || path.join(os.homedir(), '.antigravity');
|
|
72
|
+
}
|
|
73
|
+
// Comma-separated extra dirs from env. Each path's mtime contributes to SSE.
|
|
74
|
+
function extraIdeWatchDirs() {
|
|
75
|
+
const raw = process.env.KODELYTH_EXTRA_IDE_WATCH || '';
|
|
76
|
+
return raw.split(/[,;:]/).map(s => s.trim()).filter(Boolean);
|
|
77
|
+
}
|
|
48
78
|
|
|
49
79
|
function safeReadDir(p) {
|
|
50
80
|
try { return fs.readdirSync(p, { withFileTypes: true }); } catch { return []; }
|
|
@@ -308,6 +338,225 @@ function safeReadExcerpt(p, max = 800) {
|
|
|
308
338
|
} catch { return null; }
|
|
309
339
|
}
|
|
310
340
|
|
|
341
|
+
// ── IDE sessions (Claude Code / Windsurf / Antigravity) ──────────────────────
|
|
342
|
+
//
|
|
343
|
+
// Surfaces the AI coding sessions the user is actually running RIGHT NOW.
|
|
344
|
+
// Read-only filesystem stats — never reads JSONL contents (could be 27MB+).
|
|
345
|
+
// Caps the walk to keep watcher latency predictable.
|
|
346
|
+
|
|
347
|
+
const IDE_WALK_FILE_CAP = 500; // hard ceiling so a runaway dir can't hang the watcher
|
|
348
|
+
|
|
349
|
+
function decodeClaudeProjectName(name) {
|
|
350
|
+
// Claude Code encodes the project's absolute path by replacing '/' with '-'.
|
|
351
|
+
// Best-effort decode for display only — original separators are unrecoverable.
|
|
352
|
+
if (!name) return null;
|
|
353
|
+
return '/' + name.replace(/^-+/, '').replace(/-/g, '/');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function pushTopLevelFiles(sessions, dir, platform, cap, scannedRef) {
|
|
357
|
+
if (!fs.existsSync(dir)) return;
|
|
358
|
+
for (const entry of safeReadDir(dir)) {
|
|
359
|
+
if (scannedRef.value++ > cap) break;
|
|
360
|
+
if (!entry.isFile()) continue;
|
|
361
|
+
const fp = path.join(dir, entry.name);
|
|
362
|
+
const st = safeStat(fp);
|
|
363
|
+
if (!st) continue;
|
|
364
|
+
sessions.push({
|
|
365
|
+
platform,
|
|
366
|
+
session_id: entry.name,
|
|
367
|
+
project: null,
|
|
368
|
+
path: fp,
|
|
369
|
+
size_bytes: st.size,
|
|
370
|
+
modified: st.mtime.toISOString(),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function ideSessionsList({
|
|
376
|
+
claudeProjectsDir = defaultClaudeProjectsDir(),
|
|
377
|
+
windsurfDir = defaultWindsurfStateDir(),
|
|
378
|
+
windsurfNextDir = defaultWindsurfNextStateDir(),
|
|
379
|
+
cursorDir = defaultCursorWorkspaceDir(),
|
|
380
|
+
antigravityDir = defaultAntigravityStateDir(),
|
|
381
|
+
extraDirs = extraIdeWatchDirs(),
|
|
382
|
+
cwd = process.cwd(),
|
|
383
|
+
limit = 15,
|
|
384
|
+
} = {}) {
|
|
385
|
+
const sessions = [];
|
|
386
|
+
const scannedRef = { value: 0 };
|
|
387
|
+
|
|
388
|
+
// Claude Code: ~/.claude/projects/<encoded-path>/<uuid>.jsonl
|
|
389
|
+
if (fs.existsSync(claudeProjectsDir)) {
|
|
390
|
+
for (const proj of safeReadDir(claudeProjectsDir)) {
|
|
391
|
+
if (!proj.isDirectory()) continue;
|
|
392
|
+
const projDir = path.join(claudeProjectsDir, proj.name);
|
|
393
|
+
for (const f of safeReadDir(projDir)) {
|
|
394
|
+
if (scannedRef.value++ > IDE_WALK_FILE_CAP) break;
|
|
395
|
+
if (!f.isFile() || !f.name.endsWith('.jsonl')) continue;
|
|
396
|
+
const fp = path.join(projDir, f.name);
|
|
397
|
+
const st = safeStat(fp);
|
|
398
|
+
if (!st) continue;
|
|
399
|
+
sessions.push({
|
|
400
|
+
platform: 'claude-code',
|
|
401
|
+
session_id: f.name.replace(/\.jsonl$/, ''),
|
|
402
|
+
project: decodeClaudeProjectName(proj.name),
|
|
403
|
+
path: fp,
|
|
404
|
+
size_bytes: st.size,
|
|
405
|
+
modified: st.mtime.toISOString(),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (scannedRef.value > IDE_WALK_FILE_CAP) break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Windsurf (legacy) + Windsurf-Next: top-level state files only.
|
|
413
|
+
pushTopLevelFiles(sessions, windsurfDir, 'windsurf', IDE_WALK_FILE_CAP, scannedRef);
|
|
414
|
+
pushTopLevelFiles(sessions, windsurfNextDir, 'windsurf-next', IDE_WALK_FILE_CAP, scannedRef);
|
|
415
|
+
|
|
416
|
+
// Cursor: ~/Library/Application Support/Cursor/User/workspaceStorage/<hash>/
|
|
417
|
+
// Each workspace is a directory containing .vscdb sqlite files; we surface
|
|
418
|
+
// the directory mtime (proxies for last open) without reading SQLite.
|
|
419
|
+
if (fs.existsSync(cursorDir)) {
|
|
420
|
+
for (const ws of safeReadDir(cursorDir)) {
|
|
421
|
+
if (scannedRef.value++ > IDE_WALK_FILE_CAP) break;
|
|
422
|
+
if (!ws.isDirectory()) continue;
|
|
423
|
+
const wsDir = path.join(cursorDir, ws.name);
|
|
424
|
+
const st = safeStat(wsDir);
|
|
425
|
+
if (!st) continue;
|
|
426
|
+
sessions.push({
|
|
427
|
+
platform: 'cursor',
|
|
428
|
+
session_id: ws.name,
|
|
429
|
+
project: null,
|
|
430
|
+
path: wsDir,
|
|
431
|
+
size_bytes: 0,
|
|
432
|
+
modified: st.mtime.toISOString(),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Antigravity (home dir, best-effort) + per-workspace .agent/ in cwd.
|
|
438
|
+
if (fs.existsSync(antigravityDir)) {
|
|
439
|
+
const st = safeStat(antigravityDir);
|
|
440
|
+
if (st) {
|
|
441
|
+
sessions.push({
|
|
442
|
+
platform: 'antigravity',
|
|
443
|
+
session_id: 'home',
|
|
444
|
+
project: null,
|
|
445
|
+
path: antigravityDir,
|
|
446
|
+
size_bytes: 0,
|
|
447
|
+
modified: st.mtime.toISOString(),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const agentDir = path.join(cwd, '.agent');
|
|
452
|
+
if (fs.existsSync(agentDir)) {
|
|
453
|
+
const st = safeStat(agentDir);
|
|
454
|
+
if (st) {
|
|
455
|
+
sessions.push({
|
|
456
|
+
platform: 'antigravity',
|
|
457
|
+
session_id: path.basename(cwd) || 'workspace',
|
|
458
|
+
project: cwd,
|
|
459
|
+
path: agentDir,
|
|
460
|
+
size_bytes: 0,
|
|
461
|
+
modified: st.mtime.toISOString(),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// User-supplied extra paths (KODELYTH_EXTRA_IDE_WATCH).
|
|
467
|
+
// We only record an entry when the path exists; otherwise we silently skip.
|
|
468
|
+
for (const extra of extraDirs) {
|
|
469
|
+
if (!fs.existsSync(extra)) continue;
|
|
470
|
+
const st = safeStat(extra);
|
|
471
|
+
if (!st) continue;
|
|
472
|
+
sessions.push({
|
|
473
|
+
platform: 'custom',
|
|
474
|
+
session_id: path.basename(extra) || 'extra',
|
|
475
|
+
project: extra,
|
|
476
|
+
path: extra,
|
|
477
|
+
size_bytes: st.isFile() ? st.size : 0,
|
|
478
|
+
modified: st.mtime.toISOString(),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
sessions.sort((a, b) => b.modified.localeCompare(a.modified));
|
|
483
|
+
return sessions.slice(0, Math.max(1, Math.min(100, limit)));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Cheap mtime aggregator used by the SSE file-watcher tick.
|
|
487
|
+
// Returns the largest mtimeMs across IDE session files. 0 if nothing found.
|
|
488
|
+
function getMaxIdeMtime({
|
|
489
|
+
claudeProjectsDir = defaultClaudeProjectsDir(),
|
|
490
|
+
windsurfDir = defaultWindsurfStateDir(),
|
|
491
|
+
windsurfNextDir = defaultWindsurfNextStateDir(),
|
|
492
|
+
cursorDir = defaultCursorWorkspaceDir(),
|
|
493
|
+
antigravityDir = defaultAntigravityStateDir(),
|
|
494
|
+
extraDirs = extraIdeWatchDirs(),
|
|
495
|
+
cwd = process.cwd(),
|
|
496
|
+
} = {}) {
|
|
497
|
+
let maxMs = 0;
|
|
498
|
+
let scanned = 0;
|
|
499
|
+
|
|
500
|
+
// Claude Code
|
|
501
|
+
if (fs.existsSync(claudeProjectsDir)) {
|
|
502
|
+
for (const proj of safeReadDir(claudeProjectsDir)) {
|
|
503
|
+
if (!proj.isDirectory()) continue;
|
|
504
|
+
const projDir = path.join(claudeProjectsDir, proj.name);
|
|
505
|
+
for (const f of safeReadDir(projDir)) {
|
|
506
|
+
if (scanned++ > IDE_WALK_FILE_CAP) break;
|
|
507
|
+
if (!f.isFile() || !f.name.endsWith('.jsonl')) continue;
|
|
508
|
+
const st = safeStat(path.join(projDir, f.name));
|
|
509
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
510
|
+
}
|
|
511
|
+
if (scanned > IDE_WALK_FILE_CAP) break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Windsurf (legacy + next) — dir mtime + one level of files.
|
|
516
|
+
for (const dir of [windsurfDir, windsurfNextDir]) {
|
|
517
|
+
if (!fs.existsSync(dir)) continue;
|
|
518
|
+
const st = safeStat(dir);
|
|
519
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
520
|
+
for (const entry of safeReadDir(dir)) {
|
|
521
|
+
if (scanned++ > IDE_WALK_FILE_CAP) break;
|
|
522
|
+
if (!entry.isFile()) continue;
|
|
523
|
+
const fst = safeStat(path.join(dir, entry.name));
|
|
524
|
+
if (fst && fst.mtimeMs > maxMs) maxMs = fst.mtimeMs;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Cursor workspace dirs
|
|
529
|
+
if (fs.existsSync(cursorDir)) {
|
|
530
|
+
for (const ws of safeReadDir(cursorDir)) {
|
|
531
|
+
if (scanned++ > IDE_WALK_FILE_CAP) break;
|
|
532
|
+
if (!ws.isDirectory()) continue;
|
|
533
|
+
const st = safeStat(path.join(cursorDir, ws.name));
|
|
534
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Antigravity
|
|
539
|
+
if (fs.existsSync(antigravityDir)) {
|
|
540
|
+
const st = safeStat(antigravityDir);
|
|
541
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
542
|
+
}
|
|
543
|
+
const agentDir = path.join(cwd, '.agent');
|
|
544
|
+
if (fs.existsSync(agentDir)) {
|
|
545
|
+
const st = safeStat(agentDir);
|
|
546
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Extra user-supplied watch paths (KODELYTH_EXTRA_IDE_WATCH).
|
|
550
|
+
for (const extra of extraDirs) {
|
|
551
|
+
if (scanned++ > IDE_WALK_FILE_CAP) break;
|
|
552
|
+
if (!fs.existsSync(extra)) continue;
|
|
553
|
+
const st = safeStat(extra);
|
|
554
|
+
if (st && st.mtimeMs > maxMs) maxMs = st.mtimeMs;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return maxMs;
|
|
558
|
+
}
|
|
559
|
+
|
|
311
560
|
// ── token budget snapshot ────────────────────────────────────────────────────
|
|
312
561
|
|
|
313
562
|
function tokenBudgetSnapshot({ budgetDir = defaultBudgetDir() } = {}) {
|
|
@@ -343,11 +592,19 @@ module.exports = {
|
|
|
343
592
|
// catalog
|
|
344
593
|
catalogList,
|
|
345
594
|
catalogCounts,
|
|
346
|
-
// sessions
|
|
595
|
+
// sessions (orchestration)
|
|
347
596
|
sessionsList,
|
|
348
597
|
sessionDetail,
|
|
598
|
+
// ide sessions (claude code / windsurf / antigravity)
|
|
599
|
+
ideSessionsList,
|
|
349
600
|
// token budget
|
|
350
601
|
tokenBudgetSnapshot,
|
|
351
|
-
// exposed for tests
|
|
352
|
-
_internals: {
|
|
602
|
+
// exposed for tests + the file-watcher tick
|
|
603
|
+
_internals: {
|
|
604
|
+
defaultMemoryDir, defaultEvolveDir, defaultBudgetDir, defaultCoordRoot,
|
|
605
|
+
defaultClaudeProjectsDir, defaultWindsurfStateDir, defaultWindsurfNextStateDir,
|
|
606
|
+
defaultCursorWorkspaceDir, defaultAntigravityStateDir, extraIdeWatchDirs,
|
|
607
|
+
safeReadDir, safeReadJson, safeReadExcerpt,
|
|
608
|
+
getMaxIdeMtime,
|
|
609
|
+
},
|
|
353
610
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// scripts/dashboard/server.js
|
|
2
2
|
//
|
|
3
|
-
// Local observability dashboard server (v1.7.
|
|
3
|
+
// Local observability dashboard server (v1.7.5).
|
|
4
4
|
//
|
|
5
5
|
// HTTP server using ONLY Node.js built-ins. Serves:
|
|
6
6
|
// GET / → static index.html
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
// GET /api/catalog?kind=…&q=… → agents | skills | commands | rules | bundles
|
|
12
12
|
// GET /api/sessions → orchestration session list
|
|
13
13
|
// GET /api/sessions/:name → single session detail
|
|
14
|
+
// GET /api/ide-sessions → live Claude Code / Windsurf / Antigravity activity
|
|
14
15
|
// GET /api/token-budget → per-session token usage
|
|
16
|
+
// GET /api/events → SSE stream of data-changed events
|
|
15
17
|
// GET /api/health → tiny liveness probe
|
|
16
18
|
//
|
|
17
19
|
// Default bind: 127.0.0.1 (localhost ONLY). Refuses to bind 0.0.0.0 unless
|
|
@@ -32,13 +34,18 @@ const sseClients = new Set();
|
|
|
32
34
|
const MAX_SSE_CLIENTS = 10;
|
|
33
35
|
|
|
34
36
|
// Key files that signal data changes when their mtime moves.
|
|
37
|
+
// IMPORTANT: include both `routing-misses.jsonl` (always present once evolve sees
|
|
38
|
+
// any miss) AND `proposals.jsonl` (created later, after `evolve analyze` runs).
|
|
35
39
|
const WATCH_PATHS = [
|
|
36
|
-
path.join(os.homedir(), '.kodelyth', 'memory',
|
|
40
|
+
path.join(os.homedir(), '.kodelyth', 'memory', 'memories.jsonl'),
|
|
37
41
|
path.join(os.homedir(), '.kodelyth', 'evolve', 'reuse.json'),
|
|
42
|
+
path.join(os.homedir(), '.kodelyth', 'evolve', 'routing-misses.jsonl'),
|
|
38
43
|
path.join(os.homedir(), '.kodelyth', 'evolve', 'proposals.jsonl'),
|
|
44
|
+
path.join(os.homedir(), '.kodelyth', 'token-budget'), // dir mtime — changes when budget files appear
|
|
39
45
|
];
|
|
40
46
|
const lastMtimes = new Map();
|
|
41
|
-
|
|
47
|
+
let lastIdeMtime = 0; // max mtime across Claude Code / Windsurf / Antigravity sessions
|
|
48
|
+
const FILE_CHECK_MS = 3000; // poll every 3 s — "feels live" without burning CPU
|
|
42
49
|
|
|
43
50
|
function broadcastSSE(obj) {
|
|
44
51
|
const msg = `data: ${JSON.stringify(obj)}\n\n`;
|
|
@@ -55,15 +62,27 @@ function startFileWatcher() {
|
|
|
55
62
|
for (const p of WATCH_PATHS) {
|
|
56
63
|
try { lastMtimes.set(p, fs.statSync(p).mtimeMs); } catch { /* file not yet present */ }
|
|
57
64
|
}
|
|
65
|
+
try { lastIdeMtime = data._internals.getMaxIdeMtime(); } catch { lastIdeMtime = 0; }
|
|
66
|
+
|
|
58
67
|
_fileWatchTimer = setInterval(() => {
|
|
59
68
|
if (sseClients.size === 0) return; // no connected browsers — skip stat I/O
|
|
60
69
|
let changed = false;
|
|
70
|
+
|
|
71
|
+
// Static watched files (memory, evolve, token-budget).
|
|
61
72
|
for (const p of WATCH_PATHS) {
|
|
62
73
|
try {
|
|
63
74
|
const m = fs.statSync(p).mtimeMs;
|
|
64
75
|
if (lastMtimes.get(p) !== m) { changed = true; lastMtimes.set(p, m); }
|
|
65
76
|
} catch { /* file doesn't exist yet — that's fine */ }
|
|
66
77
|
}
|
|
78
|
+
|
|
79
|
+
// Live IDE activity — Claude Code / Windsurf / Antigravity.
|
|
80
|
+
// Cheap: only stats jsonl files in known dirs, capped at 500 files per tick.
|
|
81
|
+
try {
|
|
82
|
+
const m = data._internals.getMaxIdeMtime();
|
|
83
|
+
if (m > lastIdeMtime) { changed = true; lastIdeMtime = m; }
|
|
84
|
+
} catch { /* swallow — IDE watch must never break the heartbeat */ }
|
|
85
|
+
|
|
67
86
|
broadcastSSE({ type: changed ? 'data-changed' : 'heartbeat', time: new Date().toISOString() });
|
|
68
87
|
}, FILE_CHECK_MS);
|
|
69
88
|
}
|
|
@@ -241,6 +260,12 @@ function handleRequest(req, res) {
|
|
|
241
260
|
});
|
|
242
261
|
}
|
|
243
262
|
|
|
263
|
+
if (p === '/api/ide-sessions') {
|
|
264
|
+
return jsonResponse(res, 200, {
|
|
265
|
+
sessions: data.ideSessionsList({ limit: Number(q.get('limit')) || 15 }),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
const sessMatch = p.match(/^\/api\/sessions\/([A-Za-z0-9._-]+)$/);
|
|
245
270
|
if (sessMatch) {
|
|
246
271
|
const sessionName = sessMatch[1];
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
<div class="brand">
|
|
214
214
|
<span class="dot" id="liveDot"></span>
|
|
215
215
|
<h1>Kodelyth ECC</h1>
|
|
216
|
-
<span class="sub" id="hostLabel">— dashboard v1.7.
|
|
216
|
+
<span class="sub" id="hostLabel">— dashboard v1.7.4 · localhost</span>
|
|
217
217
|
</div>
|
|
218
218
|
<div class="header-right">
|
|
219
219
|
<span id="connBadge" class="pill ok" title="Real-time SSE connection status">LIVE</span>
|
|
@@ -310,6 +310,12 @@
|
|
|
310
310
|
|
|
311
311
|
<!-- ───────── SESSIONS ───────── -->
|
|
312
312
|
<section data-panel="sessions" hidden>
|
|
313
|
+
<div class="card">
|
|
314
|
+
<h2>Live IDE activity</h2>
|
|
315
|
+
<p class="muted" style="margin-top:-4px;font-size:12.5px;">Most recently modified Claude Code / Windsurf / Antigravity session files. Updates every 3 seconds via SSE.</p>
|
|
316
|
+
<div id="ideSessionsList">loading…</div>
|
|
317
|
+
</div>
|
|
318
|
+
<hr class="sep">
|
|
313
319
|
<div class="card">
|
|
314
320
|
<h2>Orchestration / swarm sessions</h2>
|
|
315
321
|
<div id="sessionsList">loading…</div>
|
|
@@ -372,7 +378,7 @@
|
|
|
372
378
|
async function loadOverview() {
|
|
373
379
|
try {
|
|
374
380
|
const o = await api('/api/overview');
|
|
375
|
-
$('#hostLabel').textContent = `— dashboard v1.7.
|
|
381
|
+
$('#hostLabel').textContent = `— dashboard v1.7.4 · ${location.host} · ${o.package_root.split('/').slice(-2).join('/')}`;
|
|
376
382
|
$('#overviewCards').innerHTML = [
|
|
377
383
|
statCard(o.catalog.agents, 'Agents'),
|
|
378
384
|
statCard(o.catalog.skills, 'Skills'),
|
|
@@ -570,13 +576,67 @@
|
|
|
570
576
|
}
|
|
571
577
|
}
|
|
572
578
|
|
|
579
|
+
// ───── ide sessions (claude code / windsurf / antigravity) ─────
|
|
580
|
+
function formatBytes(n) {
|
|
581
|
+
if (!n || n < 0) return '—';
|
|
582
|
+
if (n < 1024) return n + ' B';
|
|
583
|
+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
|
|
584
|
+
if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + ' MB';
|
|
585
|
+
return (n / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function relativeTime(iso) {
|
|
589
|
+
if (!iso) return '—';
|
|
590
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
591
|
+
if (ms < 0) return 'just now';
|
|
592
|
+
const s = Math.floor(ms / 1000);
|
|
593
|
+
if (s < 60) return s + 's ago';
|
|
594
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
595
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
596
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function loadIdeSessions() {
|
|
600
|
+
try {
|
|
601
|
+
const r = await api('/api/ide-sessions');
|
|
602
|
+
if (!r.sessions.length) {
|
|
603
|
+
$('#ideSessionsList').innerHTML = `<div class="empty">No active IDE sessions detected. Open Claude Code, Windsurf, Cursor, or an Antigravity workspace and check back. <br><span style="font-size:11px;">Add custom paths via <code>KODELYTH_EXTRA_IDE_WATCH</code> env var.</span></div>`;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const rows = r.sessions.map(s => {
|
|
607
|
+
const platformColor = s.platform === 'claude-code' ? '#a855f7'
|
|
608
|
+
: s.platform === 'windsurf' ? '#06b6d4'
|
|
609
|
+
: s.platform === 'windsurf-next' ? '#0ea5e9'
|
|
610
|
+
: s.platform === 'cursor' ? '#f97316'
|
|
611
|
+
: s.platform === 'antigravity' ? '#10b981'
|
|
612
|
+
: s.platform === 'custom' ? '#94a3b8'
|
|
613
|
+
: '#64748b';
|
|
614
|
+
const project = s.project ? `<code style="font-size:11px;color:#94a3b8;word-break:break-all;">${escapeHtml(s.project)}</code>` : '<span class="muted">—</span>';
|
|
615
|
+
return `<tr>
|
|
616
|
+
<td><span class="pill" style="background:${platformColor}22;color:${platformColor};border-color:${platformColor}55;">${escapeHtml(s.platform)}</span></td>
|
|
617
|
+
<td>${project}</td>
|
|
618
|
+
<td class="muted" title="${escapeHtml(s.modified)}">${escapeHtml(relativeTime(s.modified))}</td>
|
|
619
|
+
<td class="muted">${escapeHtml(formatBytes(s.size_bytes))}</td>
|
|
620
|
+
<td><code style="font-size:10.5px;color:#64748b;">${escapeHtml(s.session_id.slice(0, 8))}…</code></td>
|
|
621
|
+
</tr>`;
|
|
622
|
+
}).join('');
|
|
623
|
+
$('#ideSessionsList').innerHTML = `<table>
|
|
624
|
+
<thead><tr><th>Platform</th><th>Project</th><th>Last activity</th><th>Size</th><th>Session</th></tr></thead>
|
|
625
|
+
<tbody>${rows}</tbody>
|
|
626
|
+
</table>`;
|
|
627
|
+
setLastUpdated();
|
|
628
|
+
} catch (e) {
|
|
629
|
+
$('#ideSessionsList').innerHTML = `<div class="empty">failed: ${escapeHtml(e.message)}</div>`;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
573
633
|
// ───── tab dispatcher ─────
|
|
574
634
|
function onTabShown(tab) {
|
|
575
635
|
if (tab === 'overview') loadOverview();
|
|
576
636
|
if (tab === 'memory') loadMemory();
|
|
577
637
|
if (tab === 'evolve') loadEvolve();
|
|
578
638
|
if (tab === 'catalog') loadCatalog();
|
|
579
|
-
if (tab === 'sessions') loadSessions();
|
|
639
|
+
if (tab === 'sessions') { loadIdeSessions(); loadSessions(); }
|
|
580
640
|
}
|
|
581
641
|
|
|
582
642
|
// ───── real-time: SSE connection ─────
|
package/social/card-agents.svg
CHANGED
|
@@ -89,4 +89,16 @@
|
|
|
89
89
|
<rect x="28" y="356" width="644" height="44" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
90
90
|
<text x="350" y="373" font-size="11" fill="#484f58" text-anchor="middle">+ 50 more: language reviewers, build resolvers, tdd-guide, refactor-cleaner, ux-reviewer, git-rescue ...</text>
|
|
91
91
|
<text x="350" y="390" font-size="11" fill="#484f58" text-anchor="middle">All agents load automatically via semantic intent routing — no command names needed</text>
|
|
92
|
+
|
|
93
|
+
<!-- Ghost Depth Mark — brand stamp (scale 0.45×) -->
|
|
94
|
+
<g transform="translate(606, 120) scale(0.45)" opacity="0.16">
|
|
95
|
+
<polyline points="-44,4 12,100 -44,196"
|
|
96
|
+
stroke="#ffffff" stroke-width="10"
|
|
97
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
98
|
+
fill="none"/>
|
|
99
|
+
<polyline points="0,0 88,100 0,200"
|
|
100
|
+
stroke="#ffffff" stroke-width="32"
|
|
101
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
102
|
+
fill="none"/>
|
|
103
|
+
</g>
|
|
92
104
|
</svg>
|
package/social/card-install.svg
CHANGED
|
@@ -37,10 +37,22 @@
|
|
|
37
37
|
<text x="48" y="200" font-family="'Courier New', Courier, monospace" font-size="12" fill="#e6edf3"> npx kodelyth-ecc --target windsurf-project</text>
|
|
38
38
|
<text x="48" y="216" font-family="'Courier New', Courier, monospace" font-size="11" fill="#484f58"> # or Antigravity:</text>
|
|
39
39
|
<text x="48" y="232" font-family="'Courier New', Courier, monospace" font-size="12" fill="#e6edf3"> npx kodelyth-ecc --target antigravity</text>
|
|
40
|
-
<text x="48" y="256" font-family="'Courier New', Courier, monospace" font-size="11" fill="#10b981"> Installing v1.7.
|
|
40
|
+
<text x="48" y="256" font-family="'Courier New', Courier, monospace" font-size="11" fill="#10b981"> Installing v1.7.5 — 70 agents, 194 skills, 97 commands...</text>
|
|
41
41
|
<text x="48" y="292" font-family="'Courier New', Courier, monospace" font-size="11" fill="#10b981"> Done. Type /kodelyth-quickstart to begin.</text>
|
|
42
42
|
|
|
43
43
|
<!-- Footer -->
|
|
44
44
|
<text x="28" y="332" font-size="11" fill="#484f58">Node.js 18+ required</text>
|
|
45
45
|
<text x="612" y="332" font-size="11" fill="#484f58" text-anchor="end">github.com/sifxprime/kodelyth-ecc</text>
|
|
46
|
+
|
|
47
|
+
<!-- Ghost Depth Mark — brand stamp (scale 0.4×) -->
|
|
48
|
+
<g transform="translate(550, 100) scale(0.4)" opacity="0.16">
|
|
49
|
+
<polyline points="-44,4 12,100 -44,196"
|
|
50
|
+
stroke="#ffffff" stroke-width="10"
|
|
51
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
52
|
+
fill="none"/>
|
|
53
|
+
<polyline points="0,0 88,100 0,200"
|
|
54
|
+
stroke="#ffffff" stroke-width="32"
|
|
55
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
56
|
+
fill="none"/>
|
|
57
|
+
</g>
|
|
46
58
|
</svg>
|
package/social/card-main.svg
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<!-- Badge -->
|
|
19
19
|
<rect x="28" y="24" width="72" height="20" rx="10" fill="#a78bfa" fill-opacity="0.15" stroke="#a78bfa" stroke-opacity="0.4" stroke-width="1"/>
|
|
20
|
-
<text x="64" y="38" font-size="9" fill="#a78bfa" text-anchor="middle" font-weight="700" letter-spacing="0.5">v1.5
|
|
20
|
+
<text x="64" y="38" font-size="9" fill="#a78bfa" text-anchor="middle" font-weight="700" letter-spacing="0.5">v1.7.5</text>
|
|
21
21
|
|
|
22
22
|
<!-- Title -->
|
|
23
23
|
<text x="28" y="88" font-size="38" font-weight="900" letter-spacing="-1.5" fill="#ffffff">Kodelyth</text>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<line x1="28" y1="172" x2="572" y2="172" stroke="#21262d" stroke-width="1"/>
|
|
30
30
|
|
|
31
31
|
<!-- Feature list -->
|
|
32
|
-
<text x="28" y="196" font-size="12" fill="#e6edf3">→
|
|
32
|
+
<text x="28" y="196" font-size="12" fill="#e6edf3">→ 70 specialist agents, auto-routed by intent</text>
|
|
33
33
|
<text x="28" y="216" font-size="12" fill="#e6edf3">→ Compound learning — corrections encoded permanently</text>
|
|
34
34
|
<text x="28" y="236" font-size="12" fill="#e6edf3">→ Parallel commands — 5 agents, 10 min</text>
|
|
35
35
|
<text x="28" y="256" font-size="12" fill="#e6edf3">→ AI image gen — Gemini / DALL-E / SVG fallback</text>
|
|
@@ -39,4 +39,16 @@
|
|
|
39
39
|
<!-- Footer -->
|
|
40
40
|
<text x="28" y="296" font-size="11" fill="#484f58">npx kodelyth-ecc</text>
|
|
41
41
|
<text x="572" y="296" font-size="11" fill="#484f58" text-anchor="end">github.com/sifxprime/kodelyth-ecc</text>
|
|
42
|
+
|
|
43
|
+
<!-- Ghost Depth Mark — brand stamp (scale 0.35×) -->
|
|
44
|
+
<g transform="translate(505, 80) scale(0.35)" opacity="0.16">
|
|
45
|
+
<polyline points="-44,4 12,100 -44,196"
|
|
46
|
+
stroke="#ffffff" stroke-width="10"
|
|
47
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
48
|
+
fill="none"/>
|
|
49
|
+
<polyline points="0,0 88,100 0,200"
|
|
50
|
+
stroke="#ffffff" stroke-width="32"
|
|
51
|
+
stroke-linecap="round" stroke-linejoin="round"
|
|
52
|
+
fill="none"/>
|
|
53
|
+
</g>
|
|
42
54
|
</svg>
|