kodelyth-ecc 2.9.0 → 2.10.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/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.10.0 — Arena dashboard tab + docs (phases 5 & 6) (August 2026)
6
+
7
+ ### Added — Arena tab in the dashboard
8
+
9
+ The tab answers one question: **did the attacker give up?**
10
+
11
+ - **Convergence trend** per run, drawn as a block-character sparkline — no chart
12
+ library, no CDN, legible at one round or twenty. Rounds that surface nothing
13
+ render green; a round worse than the last renders red.
14
+ - **Still open** — confirmed findings GOD has *not* answered for, ranked by real
15
+ risk (`severity × confidence × exploitability`).
16
+ - **Recurring bug classes** — the same class twice is flagged `recurring`, because
17
+ one is an incident and several is a process gap.
18
+ - Runs that began with recalled memories are marked `recalled`.
19
+
20
+ Read-only. The dashboard never writes to your memory store.
21
+
22
+ `GET /api/arena[?limit=N]` → `{ available, runs, open, classes, totals }`. Raw
23
+ findings are stripped from the wire payload — the page needs counts and the open
24
+ list, not every finding on every run.
25
+
26
+ ### Fixed — a confirmed finding is not the same as an open one
27
+
28
+ `closeRound` recorded *how many* findings were left outstanding but not *which
29
+ ones*, so nothing downstream could tell a confirmed-and-fixed bug from a
30
+ confirmed-and-ignored one. The dashboard's first draft reported all ten fixed
31
+ findings as open risk — a healthy run reading as alarming, which is exactly
32
+ backwards. Rounds now persist `addressedIds`.
33
+
34
+ ### Added — `docs/arena.md`
35
+
36
+ A full feature doc: why a loop beats a review pass, what makes a finding count,
37
+ how convergence is decided, the compound-learning return path, guard proposals,
38
+ and cost control. Wired into the sitemap, the docs index, and `dashboard.md`.
39
+
40
+ ### Fixed — stale counts
41
+
42
+ The README and `CLAUDE.md` advertised 194 skills and 97 commands; the real
43
+ figures are 196 and 102. Both now match what is on disk.
44
+
45
+ **525 tests passing**, up from 516.
46
+
5
47
  ## v2.9.0 — Compound learning: the arena now remembers (phase 4) (August 2026)
6
48
 
7
49
  A finished arena run used to be knowledge thrown away. Every new run started from
package/CLAUDE.md CHANGED
@@ -7,8 +7,8 @@ Guidance for Claude Code when working with this repository.
7
7
  **Kodelyth ECC** — a production-grade AI coding toolkit:
8
8
 
9
9
  - **70 specialist agents** — debug-detective, incident-commander, load-tester, image-architect, kodelyth-memory, security-reviewer, plus 8 adversarial devil-mode agents
10
- - **194 skills** — domain knowledge, patterns, testing, security, intent routing, local memory, swarm orchestration, MCP integration
11
- - **97 commands** — slash workflows (`/tdd`, `/plan`, `/code-review`, `/team-review`, `/devil-mode`, `/debug-blitz`, `/security-audit`, ...)
10
+ - **196 skills** — domain knowledge, patterns, testing, security, intent routing, local memory, swarm orchestration, MCP integration
11
+ - **102 commands** — slash workflows (`/tdd`, `/plan`, `/code-review`, `/team-review`, `/devil-mode`, `/debug-blitz`, `/security-audit`, ...)
12
12
  - **22+ hooks** — quality gates, memory inject + capture, correction encoding, prompt-injection guard, token-budget enforcer
13
13
  - **14 rules** — always-on coding standards + semantic intent routing + memory protocol + self-improvement
14
14
 
@@ -18,15 +18,15 @@ Works with Claude Code, Windsurf, Cursor, Codex CLI, Antigravity, OpenCode, Clin
18
18
 
19
19
  ```
20
20
  agents/ → 70 specialist subagents (planner, code-reviewer, debug-detective, devil-mode crew, ...)
21
- commands/ → 97 slash commands (8 parallel multi-agent, rest single-agent)
22
- skills/ → 194 workflow + domain knowledge files (loadable via slash commands)
21
+ commands/ → 102 slash commands (8 parallel multi-agent, 1 adversarial loop, rest single-agent)
22
+ skills/ → 196 workflow + domain knowledge files (loadable via slash commands)
23
23
  hooks/ → 22+ automations (pre-commit, session memory, prompt-injection guard, token-budget)
24
24
  rules/ → 14 always-on guidelines (agent-intent-routing, self-improvement, memory-protocol, ...)
25
25
  scripts/ → Node.js utilities: MCP server, dashboard, swarm, replay, router, memory, supply-chain
26
26
  bundles/ → 3 power bundles (indie-hacker, red-team, enterprise)
27
27
  actions/ → GitHub Action (CI/CD integration for PR review)
28
- docs/ → Feature docs (mcp.md, dashboard.md, swarm.md, replay.md, evolve.md, supply-chain.md)
29
- tests/ → 516 passing tests across 27 test files
28
+ docs/ → Feature docs (arena.md, mcp.md, dashboard.md, swarm.md, replay.md, evolve.md, supply-chain.md)
29
+ tests/ → 525 passing tests across 29 test files
30
30
  ```
31
31
 
32
32
  ## Running Tests
@@ -80,6 +80,7 @@ These fire multiple specialist agents simultaneously:
80
80
  | `/pre-release` | release-captain + security-reviewer + code-reviewer | Go/no-go verdict before shipping |
81
81
  | `/onboard` | code-explorer + architect + doc-updater | Understand any codebase in 15 minutes |
82
82
  | `/devil-mode` | prompt-injection-hunter + supply-chain-auditor + secret-hunter + backdoor-hunter | Adversarial sweep (use `--all` for all 8) |
83
+ | `/arena` | GOD crew vs EVIL crew, looped | Ship something that must not break — runs until the attacker gives up |
83
84
 
84
85
  ## Key Commands
85
86
 
package/README.md CHANGED
@@ -33,7 +33,7 @@
33
33
 
34
34
  </div>
35
35
 
36
- **Kodelyth ECC** is a production-grade AI coding toolkit — **70 specialist agents (incl. an 8-agent devil-mode adversarial crew), 194 skills, 97 commands**, a god-tier **semantic intent-routing system**, local self-learning memory, MCP server, swarm orchestrator, and an observability dashboard — all local, zero telemetry.
36
+ **Kodelyth ECC** is a production-grade AI coding toolkit — **70 specialist agents (incl. an 8-agent devil-mode adversarial crew), 196 skills, 102 commands**, a god-tier **semantic intent-routing system**, local self-learning memory, MCP server, swarm orchestrator, and an observability dashboard — all local, zero telemetry.
37
37
 
38
38
  Now bundled with:
39
39
 
@@ -73,7 +73,7 @@ You never typed `use debug-detective`. You didn't have to. The toolkit read the
73
73
  | **Intent routing** | Plain-language → right specialist via 10-tier priority rules | Mostly missing — you memorize names |
74
74
  | **70 agents** | Specialists with playbooks, severity calibration, real commands | Often persona-only ("you are a senior engineer...") |
75
75
  | **194 skills** | Domain knowledge files agents read on demand | Rarely separated from agents |
76
- | **97 commands** | Slash workflows (`/tdd`, `/devil-mode`, `/team-review`) | Limited or none |
76
+ | **102 commands** | Slash workflows (`/tdd`, `/arena`, `/devil-mode`, `/team-review`) | Limited or none |
77
77
  | **8 parallel commands** | Fire 3-8 agents simultaneously, aggregate results | Rare |
78
78
  | **Compound memory** | BM25 local recall + auto-inject + project lessons | Cloud-only or absent |
79
79
  | **22+ hooks** | Quality gates, secret scan, project-DNA detection | Often missing |
@@ -92,6 +92,7 @@ You never typed `use debug-detective`. You didn't have to. The toolkit read the
92
92
  | **Local BM25 self-learning memory** | ✅ | ❌ | ❌ | ❌ |
93
93
  | **Compound learning from corrections** | ✅ `tasks/lessons.md` | ❌ | ❌ | ❌ |
94
94
  | **Adversarial / red-team agents** | ✅ 8 (devil-mode) | ❌ | ❌ | ❌ |
95
+ | **Adversarial build/attack loop** | ✅ `/arena` — scored, verified, converges | ❌ | ❌ | ❌ |
95
96
  | Quality hooks | ✅ 22+ | Some | ❌ | ❌ |
96
97
  | IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
97
98
  | Telemetry | ❌ none | Varies | ❌ | Varies |
@@ -372,6 +373,7 @@ Eight commands fire multiple specialist agents simultaneously and aggregate thei
372
373
  | `/pre-release` | release-captain + security-reviewer + code-reviewer | 30 min → 8 min |
373
374
  | `/onboard` | code-explorer + architect + doc-updater | 45 min → 12 min |
374
375
  | `/devil-mode` | 8 adversarial agents (see below) | Hours → 20 min |
376
+ | `/arena` | GOD crew vs EVIL crew, looped until the attacker gives up | Days → 1 session |
375
377
 
376
378
  Each command waits for all agents to complete, then returns a single **Team Review Report** with findings bucketed by severity: CRITICAL → HIGH → MEDIUM → LOW.
377
379
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.9.0
1
+ 2.10.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
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",
@@ -176,6 +176,10 @@ function closeRound(run) {
176
176
  });
177
177
 
178
178
  // Attach whether GOD actually finished its side of the round.
179
+ // Persist WHICH findings GOD answered for, not just how many are left over.
180
+ // Without the ids, nothing downstream can tell a confirmed-and-fixed finding
181
+ // from confirmed-and-ignored — the dashboard would report every fix as open risk.
182
+ verdict.addressedIds = [...(run.pending.addressedIds || [])];
179
183
  verdict.godComplete = completion.complete;
180
184
  verdict.unverifiedArtifacts = completion.unverifiedArtifacts;
181
185
  verdict.outstandingFindings = completion.outstandingFindings;
@@ -580,9 +580,120 @@ function tokenBudgetSnapshot({ budgetDir = defaultBudgetDir() } = {}) {
580
580
  return { sessions: sessions.slice(0, 50), total_tokens: total };
581
581
  }
582
582
 
583
+ // ── Arena ────────────────────────────────────────────────────────────────────
584
+ //
585
+ // The dashboard's job here is to make ONE thing legible: did the attacker give
586
+ // up? A run whose new-finding count falls to zero is converging. A flat or
587
+ // rising line means the code has deeper problems, or the scope is too broad for
588
+ // EVIL to ever exhaust — either way, look before shipping.
589
+
590
+ function arenaSnapshot({ runLimit = 20 } = {}) {
591
+ let arenaState = null;
592
+ let learn = null;
593
+ try { arenaState = require('../arena/state.js'); } catch { /* arena is optional */ }
594
+ try { learn = require('../arena/learn.js'); } catch { /* */ }
595
+ if (!arenaState) return { available: false, runs: [], classes: [], open: [], totals: {} };
596
+
597
+ let list = [];
598
+ try { list = arenaState.listRuns() || []; } catch { /* no runs yet */ }
599
+
600
+ const runs = list.slice(0, Math.max(1, Math.min(100, runLimit))).map(meta => {
601
+ let run = null;
602
+ try { run = arenaState.load(meta.runId); } catch { /* skip unreadable */ }
603
+ if (!run) return null;
604
+
605
+ const rounds = run.rounds || [];
606
+ const settled = [];
607
+ const seen = new Set();
608
+ for (const r of rounds) {
609
+ for (const f of r.findings || []) {
610
+ if (seen.has(f.id)) continue;
611
+ seen.add(f.id);
612
+ settled.push(f);
613
+ }
614
+ }
615
+
616
+ return {
617
+ runId: run.runId,
618
+ task: run.task,
619
+ scope: run.scope || '.',
620
+ status: run.status,
621
+ stopReason: run.stopReason || null,
622
+ startedAt: run.startedAt,
623
+ rounds: rounds.length,
624
+ tokens: run.spent?.tokens || 0,
625
+ // The trend IS the story: new findings per round, which should fall to zero.
626
+ trend: rounds.map(r => r.counts?.new || 0),
627
+ confirmed: settled.filter(f => f.verdict === 'confirmed').length,
628
+ refuted: settled.filter(f => f.verdict === 'refuted').length,
629
+ unverified: settled.filter(f => f.verdict === 'unverified').length,
630
+ artifacts: rounds.reduce((n, r) => n + (r.artifacts?.length || 0), 0),
631
+ recalled: run.priorKnowledge ? true : false,
632
+ findings: settled,
633
+ };
634
+ }).filter(Boolean);
635
+
636
+ // Still-open risk across every run, worst first. A confirmed finding nobody
637
+ // fixed is the single most useful thing this page can surface.
638
+ const open = [];
639
+ // Findings GOD answered for are not open risk. Counting a confirmed-and-fixed
640
+ // bug as outstanding would make a healthy run look alarming.
641
+ const addressed = new Set();
642
+ for (const meta of list.slice(0, runLimit)) {
643
+ try {
644
+ const full = arenaState.load(meta.runId);
645
+ for (const rd of full?.rounds || []) for (const id of rd.addressedIds || []) addressed.add(id);
646
+ } catch { /* */ }
647
+ }
648
+ for (const r of runs) {
649
+ for (const f of r.findings) {
650
+ if (f.verdict !== 'confirmed') continue;
651
+ if (addressed.has(f.id)) continue; // GOD answered for this one
652
+ open.push({
653
+ runId: r.runId, scope: r.scope, title: f.title,
654
+ file: f.file, line: f.line, severity: f.severity,
655
+ risk: f.risk || 0,
656
+ class: learn ? learn.classify(f) : null,
657
+ });
658
+ }
659
+ }
660
+ open.sort((a, b) => b.risk - a.risk);
661
+
662
+ // Which bug classes keep coming back — the signal that a guard belongs upstream.
663
+ const classCount = new Map();
664
+ if (learn) {
665
+ for (const r of runs) {
666
+ for (const f of r.findings) {
667
+ if (f.verdict !== 'confirmed') continue;
668
+ const c = learn.classify(f);
669
+ classCount.set(c, (classCount.get(c) || 0) + 1);
670
+ }
671
+ }
672
+ }
673
+ const classes = [...classCount.entries()]
674
+ .map(([name, count]) => ({ name, count }))
675
+ .sort((a, b) => b.count - a.count);
676
+
677
+ const totals = {
678
+ runs: runs.length,
679
+ converged: runs.filter(r => r.status === 'converged').length,
680
+ confirmed: runs.reduce((n, r) => n + r.confirmed, 0),
681
+ refuted: runs.reduce((n, r) => n + r.refuted, 0),
682
+ artifacts: runs.reduce((n, r) => n + r.artifacts, 0),
683
+ tokens: runs.reduce((n, r) => n + r.tokens, 0),
684
+ };
685
+
686
+ // Drop the raw findings from the wire payload — the page needs the counts and
687
+ // the open list, not every finding on every run.
688
+ const wireRuns = runs.map(({ findings, ...rest }) => rest);
689
+ return { available: true, runs: wireRuns, open: open.slice(0, 40), classes, totals };
690
+ }
691
+
583
692
  module.exports = {
584
693
  // overview
585
694
  overview,
695
+ // arena
696
+ arenaSnapshot,
586
697
  // memory
587
698
  memoryStats,
588
699
  recentMemories,
@@ -304,6 +304,10 @@ function handleRequest(req, res) {
304
304
  return jsonResponse(res, 200, { ok: true, ...s });
305
305
  }
306
306
 
307
+ if (p === '/api/arena') {
308
+ return jsonResponse(res, 200, data.arenaSnapshot({ runLimit: Number(q.get('limit')) || 20 }));
309
+ }
310
+
307
311
  if (p === '/api/codebase') {
308
312
  const cb = require('../codebase/index.js');
309
313
  return jsonResponse(res, 200, cb.dashboardSnapshot());
@@ -223,6 +223,7 @@
223
223
  <button data-tab="rtk">Token Savings</button>
224
224
  <button data-tab="memory">Memory</button>
225
225
  <button data-tab="codebase">Codebase</button>
226
+ <button data-tab="arena">Arena</button>
226
227
  <button data-tab="evolve">Evolve</button>
227
228
  <button data-tab="catalog">Catalog</button>
228
229
  <button data-tab="sessions">Sessions</button>
@@ -320,6 +321,50 @@ kodelyth-ecc codebase query detect_changes '{}'</pre>
320
321
  </section>
321
322
 
322
323
  <!-- ───────── EVOLVE ───────── -->
324
+ <!-- ───────── ARENA ───────── -->
325
+ <section data-panel="arena" hidden>
326
+ <div class="grid" id="arenaCards"></div>
327
+ <hr class="sep">
328
+ <div class="card">
329
+ <h2>Did the attacker give up?</h2>
330
+ <p class="muted" style="font-size:12.5px;margin:-4px 0 14px;">
331
+ New findings per round. Falling to zero means EVIL ran out of ideas.
332
+ A flat or rising line means stop and look — either the code has deeper
333
+ problems, or the scope is too broad to ever exhaust.
334
+ </p>
335
+ <div id="arenaRuns">loading…</div>
336
+ </div>
337
+ <hr class="sep">
338
+ <div class="row">
339
+ <div class="card">
340
+ <h2>Still open</h2>
341
+ <p class="muted" style="font-size:12.5px;margin:-4px 0 12px;">
342
+ Confirmed and not yet answered for, worst risk first.
343
+ </p>
344
+ <div id="arenaOpen">loading…</div>
345
+ </div>
346
+ <div class="card">
347
+ <h2>Recurring bug classes</h2>
348
+ <p class="muted" style="font-size:12.5px;margin:-4px 0 12px;">
349
+ One is an incident. Several is a process gap — the guard belongs upstream.
350
+ </p>
351
+ <div id="arenaClasses">loading…</div>
352
+ </div>
353
+ </div>
354
+ <hr class="sep">
355
+ <div class="card">
356
+ <h2>Compound learning</h2>
357
+ <p class="muted" style="font-size:12.5px;margin:-4px 0 10px;">
358
+ Store what a run proved so the next run on that scope starts informed:
359
+ </p>
360
+ <div><code>kodelyth-ecc arena learn &lt;run-id&gt; --commit</code></div>
361
+ <p class="muted" style="margin-top:12px;font-size:12.5px;">
362
+ Nothing is written without <code>--commit</code>. The dashboard NEVER
363
+ writes to your memory store.
364
+ </p>
365
+ </div>
366
+ </section>
367
+
323
368
  <section data-panel="evolve" hidden>
324
369
  <div class="grid" id="evolveCards"></div>
325
370
  <hr class="sep">
@@ -527,6 +572,86 @@ kodelyth-ecc codebase query detect_changes '{}'</pre>
527
572
  }
528
573
 
529
574
  // ───── evolve ─────
575
+ // A compact sparkline made of block characters — no chart library, no CDN,
576
+ // and it stays legible when the run has one round or twenty.
577
+ function trendBar(trend) {
578
+ if (!trend || !trend.length) return '<span class="dim">no rounds yet</span>';
579
+ const max = Math.max(...trend, 1);
580
+ const blocks = '▁▂▃▄▅▆▇█';
581
+ return trend.map((n, i) => {
582
+ const idx = n === 0 ? 0 : Math.min(blocks.length - 1, Math.ceil((n / max) * (blocks.length - 1)));
583
+ const colour = n === 0 ? '#22c55e' : (i > 0 && n < trend[i - 1] ? '#eab308' : '#ef4444');
584
+ return `<span title="round ${i + 1}: ${n} new" style="color:${colour};font-size:18px;line-height:1;">${blocks[idx]}</span>`;
585
+ }).join('');
586
+ }
587
+
588
+ function arenaStatusPill(status) {
589
+ const map = {
590
+ converged: ['ok', 'converged'],
591
+ running: ['', 'running'],
592
+ exhausted: ['pending', 'max rounds'],
593
+ aborted: ['pending', 'stopped'],
594
+ };
595
+ const [cls, label] = map[status] || ['', status || 'unknown'];
596
+ return `<span class="pill ${cls}">${escapeHtml(label)}</span>`;
597
+ }
598
+
599
+ async function loadArena() {
600
+ try {
601
+ const a = await api('/api/arena');
602
+ if (!a.available) {
603
+ $('#arenaCards').innerHTML = '';
604
+ $('#arenaRuns').innerHTML = '<div class="empty">Arena is not installed.</div>';
605
+ $('#arenaOpen').innerHTML = '';
606
+ $('#arenaClasses').innerHTML = '';
607
+ return;
608
+ }
609
+
610
+ $('#arenaCards').innerHTML = [
611
+ statCard(a.totals.runs, 'Runs'),
612
+ statCard(a.totals.confirmed, 'Bugs confirmed'),
613
+ statCard(a.totals.refuted, 'False positives caught'),
614
+ statCard(a.open.length, 'Still open'),
615
+ statCard(a.totals.artifacts, 'Verified fixes'),
616
+ ].join('');
617
+
618
+ $('#arenaRuns').innerHTML = a.runs.length
619
+ ? `<div class="table-wrap"><table>
620
+ <thead><tr><th>Run</th><th>Scope</th><th>Trend</th><th>Rounds</th><th>Confirmed</th><th>Refuted</th><th>Tokens</th><th>Status</th></tr></thead>
621
+ <tbody>${a.runs.map(r => `<tr>
622
+ <td><span class="strong">${escapeHtml(r.task || r.runId)}</span>${r.recalled ? ' <span class="pill" title="started with recalled memories from past runs">recalled</span>' : ''}</td>
623
+ <td><code>${escapeHtml(r.scope)}</code></td>
624
+ <td>${trendBar(r.trend)}</td>
625
+ <td>${r.rounds}</td>
626
+ <td>${r.confirmed}</td>
627
+ <td>${r.refuted}</td>
628
+ <td>${(r.tokens || 0).toLocaleString()}</td>
629
+ <td>${arenaStatusPill(r.status)}</td>
630
+ </tr>`).join('')}</tbody></table></div>`
631
+ : '<div class="empty">No arena runs yet. Start one with <code>kodelyth-ecc arena start --task "..."</code></div>';
632
+
633
+ $('#arenaOpen').innerHTML = a.open.length
634
+ ? a.open.map(f => `<div style="margin-bottom:12px;">
635
+ <div class="strong">${escapeHtml(f.title)}</div>
636
+ <div class="muted" style="font-size:12px;margin-top:3px;">
637
+ <span class="pill">${escapeHtml(f.severity)}</span>
638
+ risk ${f.risk}${f.class ? ` · ${escapeHtml(f.class)}` : ''}
639
+ ${f.file ? ` · <code>${escapeHtml(f.file)}${f.line ? ':' + f.line : ''}</code>` : ''}
640
+ </div>
641
+ </div>`).join('')
642
+ : '<div class="empty">Nothing open. Every confirmed finding was either fixed or refuted.</div>';
643
+
644
+ $('#arenaClasses').innerHTML = a.classes.length
645
+ ? a.classes.map(c => `<div style="display:flex;justify-content:space-between;margin-bottom:8px;">
646
+ <span>${escapeHtml(c.name)}${c.count > 1 ? ' <span class="pill pending">recurring</span>' : ''}</span>
647
+ <span class="strong">${c.count}</span>
648
+ </div>`).join('')
649
+ : '<div class="empty">No confirmed findings yet.</div>';
650
+ } catch (err) {
651
+ $('#arenaRuns').innerHTML = `<div class="empty">Could not load arena data: ${escapeHtml(String(err.message || err))}</div>`;
652
+ }
653
+ }
654
+
530
655
  async function loadEvolve() {
531
656
  try {
532
657
  const e = await api('/api/evolve');
@@ -838,6 +963,7 @@ kodelyth-ecc codebase query detect_changes '{}'</pre>
838
963
  if (tab === 'rtk') loadRtk();
839
964
  if (tab === 'memory') loadMemory();
840
965
  if (tab === 'codebase') loadCodebase();
966
+ if (tab === 'arena') loadArena();
841
967
  if (tab === 'evolve') loadEvolve();
842
968
  if (tab === 'catalog') loadCatalog();
843
969
  if (tab === 'sessions') { loadIdeSessions(); loadSessions(); }