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 +42 -0
- package/CLAUDE.md +7 -6
- package/README.md +4 -2
- package/VERSION +1 -1
- package/package.json +1 -1
- package/scripts/arena/arena.js +4 -0
- package/scripts/dashboard/data.js +111 -0
- package/scripts/dashboard/server.js +4 -0
- package/scripts/dashboard/static/index.html +126 -0
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
|
-
- **
|
|
11
|
-
- **
|
|
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/ →
|
|
22
|
-
skills/ →
|
|
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/ →
|
|
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),
|
|
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
|
-
| **
|
|
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.
|
|
1
|
+
2.10.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
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",
|
package/scripts/arena/arena.js
CHANGED
|
@@ -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 <run-id> --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(); }
|