kodelyth-ecc 1.7.3 → 1.7.4
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 +39 -0
- package/GITHUB_SETUP.md +553 -0
- package/README.md +51 -6
- package/VERSION +1 -1
- package/actions/ecc-review/README.md +7 -7
- 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-install.svg +1 -1
- package/social/card-main.svg +2 -2
- package/social/facebook-v150.svg +4 -4
- package/social/github-social-preview.svg +3 -3
- package/social/hype-compound-learning.svg +117 -0
- package/social/hype-devil-mode.svg +228 -0
- package/social/hype-mcp-server.svg +136 -0
- package/social/hype-parallel-agents.svg +150 -0
- package/social/hype-stats-hero.svg +126 -0
- package/social/readme-hero.svg +4 -4
- package/social/section-mcp.svg +1 -1
- package/social/twitter-threads.md +621 -0
- package/social/x-card-agents-grid.svg +2 -2
- package/social/x-card-free.svg +2 -2
- package/social/x-card-hook.svg +5 -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
package/README.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="social/readme-hero.svg" alt="Kodelyth ECC — AI Agent Toolkit" width="900"/>
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/kodelyth-ecc)
|
|
6
|
+
[](https://www.npmjs.com/package/kodelyth-ecc)
|
|
7
|
+
[](https://github.com/sifxprime/kodelyth-ecc/stargazers)
|
|
5
8
|
[](LICENSE)
|
|
9
|
+

|
|
6
10
|

|
|
7
11
|

|
|
8
12
|

|
|
9
13
|

|
|
10
14
|

|
|
11
|
-

|
|
12
15
|

|
|
13
16
|

|
|
14
17
|

|
|
@@ -74,7 +77,7 @@ You never typed `use debug-detective`. You didn't have to. The toolkit read the
|
|
|
74
77
|
| Quality hooks | ✅ 22+ | Some | ❌ | ❌ |
|
|
75
78
|
| IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
|
|
76
79
|
| Telemetry | ❌ none | Varies | ❌ | Varies |
|
|
77
|
-
| Test coverage | ✅
|
|
80
|
+
| Test coverage | ✅ 348 tests | ❌ | ❌ | ❌ |
|
|
78
81
|
| Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
|
|
79
82
|
|
|
80
83
|
---
|
|
@@ -125,6 +128,30 @@ Feature depth varies by platform — hooks are a Claude Code native format, and
|
|
|
125
128
|
|
|
126
129
|
Hooks use Claude Code's JSON settings format — no equivalent exists on other platforms. Cursor reads rules and skills from `.cursor/`; its agent system uses a different format not yet compatible with ECC agents.
|
|
127
130
|
|
|
131
|
+
### Memory & dashboard reality per IDE
|
|
132
|
+
|
|
133
|
+
Memory storage is a single shared file at `~/.kodelyth/memory/memories.jsonl` — every IDE on the same machine reads and writes the **same** memories. The only thing that varies is *how* memories surface:
|
|
134
|
+
|
|
135
|
+
| Platform | Auto-recall on every prompt | Auto-capture on success | Manual recall via MCP tool | Dashboard "Live IDE activity" |
|
|
136
|
+
|---|---|---|---|---|
|
|
137
|
+
| **Claude Code** | ✓ (hook) | ✓ (hook) | ✓ | ✓ Claude session files |
|
|
138
|
+
| **Windsurf** | — | — | ✓ `recall_memory` | ✓ Windsurf + Windsurf-Next state |
|
|
139
|
+
| **Cursor** | — | — | ✓ `recall_memory` | ✓ workspace storage dirs |
|
|
140
|
+
| **Codex CLI** | — | — | ✓ `recall_memory` | — |
|
|
141
|
+
| **Antigravity** | — | — | ✓ `recall_memory` | ✓ `.agent/` in cwd |
|
|
142
|
+
| **Roo Code / Aider / Kimi / Cline / Gemini CLI / OpenCode** | — | — | ✓ if MCP-capable | — |
|
|
143
|
+
|
|
144
|
+
What this means in practice:
|
|
145
|
+
- A memory captured in **Claude Code** is recall-able from every other IDE the same day — the file is shared.
|
|
146
|
+
- In **Windsurf / Cursor / Codex / Antigravity**, the AI does NOT auto-fire memory recall; the `rules/common/memory-protocol.md` rule (installed automatically) tells the AI to call the `recall_memory` MCP tool proactively at the start of substantive prompts.
|
|
147
|
+
- The dashboard's **Sessions → Live IDE activity** tab surfaces session files for Claude Code, Windsurf, Windsurf-Next, Cursor, and Antigravity. Add custom paths via the `KODELYTH_EXTRA_IDE_WATCH` env var (comma-separated).
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Watch additional paths in the dashboard
|
|
151
|
+
export KODELYTH_EXTRA_IDE_WATCH="$HOME/my-agent-logs,$HOME/other-tool/state"
|
|
152
|
+
npx kodelyth-ecc dashboard
|
|
153
|
+
```
|
|
154
|
+
|
|
128
155
|
### Option 2 — npx from GitHub (always latest commit)
|
|
129
156
|
|
|
130
157
|
```bash
|
|
@@ -352,7 +379,13 @@ What's inside:
|
|
|
352
379
|
| **Memory** | Browse, search, and manage your local BM25 memory store |
|
|
353
380
|
| **Evolve** | Self-improving memory — review AI-proposed refinements |
|
|
354
381
|
| **Catalog** | Full searchable index of all 70 agents, 194 skills, 97 commands |
|
|
355
|
-
| **Sessions** |
|
|
382
|
+
| **Sessions** | **Live IDE activity** (Claude Code, Windsurf, Windsurf-Next, Cursor, Antigravity) + orchestration/swarm sessions |
|
|
383
|
+
|
|
384
|
+
Real-time:
|
|
385
|
+
- SSE push every 3 seconds, only when at least one browser tab is connected (zero CPU otherwise)
|
|
386
|
+
- Watches memory writes, evolve signals, token-budget changes, and IDE session files
|
|
387
|
+
- "Last activity" auto-updates without a page refresh
|
|
388
|
+
- Set `KODELYTH_EXTRA_IDE_WATCH=path1,path2` to watch additional paths
|
|
356
389
|
|
|
357
390
|
Security design:
|
|
358
391
|
- **GET-only** — no write endpoints accessible from the UI
|
|
@@ -752,12 +785,24 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for templates, checklists, and the full K
|
|
|
752
785
|
|
|
753
786
|
## Changelog
|
|
754
787
|
|
|
755
|
-
See [CHANGELOG.md](CHANGELOG.md). v1.7.
|
|
788
|
+
See [CHANGELOG.md](CHANGELOG.md). v1.7.4 highlights:
|
|
789
|
+
|
|
790
|
+
- **Real-time IDE-aware dashboard** — Sessions tab now surfaces live Claude Code / Windsurf / Windsurf-Next / Cursor / Antigravity activity; SSE poll 10 s → 3 s; new `/api/ide-sessions` endpoint
|
|
791
|
+
- **Cross-IDE memory protocol** — `rules/common/memory-protocol.md` now explicitly instructs non-Claude IDEs (Windsurf, Cursor, Codex, Antigravity) to call the `recall_memory` and `capture_memory` MCP tools proactively
|
|
792
|
+
- **`KODELYTH_EXTRA_IDE_WATCH` env var** — add custom watch paths to the dashboard (comma/semicolon/colon-separated)
|
|
793
|
+
- **Watch-path fixes** — broken `proposals.jsonl`-only watch now also watches `routing-misses.jsonl` and the `token-budget/` directory
|
|
794
|
+
- **+12 dashboard tests** for IDE platform coverage and cross-platform path resolution
|
|
795
|
+
|
|
796
|
+
v1.7.3 highlights:
|
|
797
|
+
|
|
798
|
+
- **Social hype pack** — 5 X/Twitter SVG cards + 5 thread scripts, full SVG version sync, GitHub issue templates, repo polish
|
|
799
|
+
|
|
800
|
+
v1.7.0 highlights:
|
|
756
801
|
|
|
757
|
-
- **MCP Server** — 16 tools, 6 prompts,
|
|
802
|
+
- **MCP Server** — 16 tools, 6 prompts, 377 resources; stdio JSON-RPC; zero telemetry; works with Claude Desktop, LangGraph, AutoGen, CrewAI, OpenAI Agents SDK
|
|
758
803
|
- **Dashboard hardening** — DNS rebinding protection, shell injection fix (`execFileSync`), CSP on all responses, SSE client cap (max 10), TOCTOU fixes
|
|
759
804
|
- **Devil Mode** — 8 adversarial agents, `--pre-public` / `--pre-launch` / `--all` flags, real bash-grep patterns and remediation playbooks
|
|
760
|
-
- **336 tests** passing across 24 test files
|
|
805
|
+
- **336 tests** passing across 24 test files (now 348 at v1.7.4)
|
|
761
806
|
|
|
762
807
|
v1.5.8 highlights:
|
|
763
808
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.7.
|
|
1
|
+
1.7.4
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0
|
|
31
31
|
|
|
32
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
32
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
33
33
|
with:
|
|
34
34
|
bundle: red-team
|
|
35
35
|
fail-on: critical
|
|
@@ -75,7 +75,7 @@ That's it. Every PR now gets reviewed by ECC's adversarial crew and the findings
|
|
|
75
75
|
To run a custom set, use `agents:` instead:
|
|
76
76
|
|
|
77
77
|
```yaml
|
|
78
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
78
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
79
79
|
with:
|
|
80
80
|
agents: secret-hunter,license-violation-finder,jailbreak-tester
|
|
81
81
|
```
|
|
@@ -87,7 +87,7 @@ To run a custom set, use `agents:` instead:
|
|
|
87
87
|
### 1. Indie Hacker — fast feedback, never block
|
|
88
88
|
|
|
89
89
|
```yaml
|
|
90
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
90
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
91
91
|
with:
|
|
92
92
|
bundle: indie-hacker
|
|
93
93
|
fail-on: none
|
|
@@ -99,7 +99,7 @@ PR gets a comment, but CI never fails. You decide what to address.
|
|
|
99
99
|
### 2. Red Team — full adversarial sweep, block on critical
|
|
100
100
|
|
|
101
101
|
```yaml
|
|
102
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
102
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
103
103
|
with:
|
|
104
104
|
bundle: red-team
|
|
105
105
|
fail-on: critical
|
|
@@ -111,7 +111,7 @@ Default. Recommended for any user-facing product.
|
|
|
111
111
|
### 3. Enterprise — strict, block on high
|
|
112
112
|
|
|
113
113
|
```yaml
|
|
114
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
114
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
115
115
|
with:
|
|
116
116
|
bundle: enterprise
|
|
117
117
|
fail-on: high
|
|
@@ -121,7 +121,7 @@ Default. Recommended for any user-facing product.
|
|
|
121
121
|
### 4. Pre-public-release — secret + license + AI safety only
|
|
122
122
|
|
|
123
123
|
```yaml
|
|
124
|
-
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.
|
|
124
|
+
- uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.7.3
|
|
125
125
|
with:
|
|
126
126
|
agents: secret-hunter,license-violation-finder,prompt-injection-hunter
|
|
127
127
|
fail-on: critical
|
|
@@ -236,7 +236,7 @@ Reduce findings by tightening `agents:` or `max-files:`. The action caps to firs
|
|
|
236
236
|
|
|
237
237
|
- [Kodelyth ECC repo](https://github.com/sifxprime/kodelyth-ecc) — the toolkit this action runs
|
|
238
238
|
- [`/devil-mode` command](../../commands/devil-mode.md) — same agents, run locally
|
|
239
|
-
- [Devil Roadmap](../../) — the broader v1.
|
|
239
|
+
- [Devil Roadmap](../../) — the broader v1.7.3 → v2.0.0 plan
|
|
240
240
|
|
|
241
241
|
---
|
|
242
242
|
|
package/bundles/enterprise.md
CHANGED
package/bundles/indie-hacker.md
CHANGED
package/bundles/red-team.md
CHANGED
package/docs/supply-chain.md
CHANGED
|
@@ -102,7 +102,7 @@ Exits `0` on `ok=true`, `1` otherwise. With `--json`, prints the full report:
|
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
# 1) Download the manifest from the GitHub release that matches your installed version.
|
|
105
|
-
gh release download v1.7.
|
|
105
|
+
gh release download v1.7.3 -p kodelyth-ecc-manifest.json -O /tmp/manifest.json
|
|
106
106
|
|
|
107
107
|
# 2) Run verify against your installed copy.
|
|
108
108
|
npx kodelyth-ecc verify --root "$(npm root -g)/kodelyth-ecc" --manifest /tmp/manifest.json
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 194 skills, 97 commands, parallel multi-agent commands, semantic intent routing, self-learning memory, and a built-in MCP server (16 tools / 6 prompts / 377 resources) that bridges to Claude Desktop, LangGraph, AutoGen, CrewAI, and OpenAI Agents SDK. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, RooCode, Aider, Kimi, and Gemini CLI.",
|
|
5
5
|
"author": "Kodelyth <github.com/sifxprime>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"url": "https://github.com/sifxprime/kodelyth-ecc.git"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://github.com/sifxprime/kodelyth-ecc",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/sifxprime/kodelyth-ecc/issues"
|
|
14
|
+
},
|
|
12
15
|
"keywords": [
|
|
13
16
|
"claude-code",
|
|
14
17
|
"windsurf",
|
|
@@ -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.4).
|
|
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.4).
|
|
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];
|