diff --git a/README.md b/README.md index 7644ed38..9bff6c07 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ This repo is the raw code only. The guides explain everything. - **Media and launch tooling** — `manim-video`, `remotion-video-creation`, and upgraded social publishing surfaces make technical explainers and launch content part of the same system. - **Framework and product surface growth** — `nestjs-patterns`, richer Codex/OpenCode install surfaces, and expanded cross-harness packaging keep the repo usable beyond Claude Code alone. - **ECC 2.0 alpha is in-tree** — the Rust control-plane prototype in `ecc2/` now builds locally and exposes `dashboard`, `start`, `sessions`, `status`, `stop`, `resume`, and `daemon` commands. It is usable as an alpha, not yet a general release. -- **Operator status snapshots** — `ecc status --markdown --write status.md` turns the local state store into a portable handoff covering active sessions, skill-run health, install health, and pending governance events. +- **Operator status snapshots** — `ecc status --markdown --write status.md` turns the local state store into a portable handoff covering readiness, active sessions, skill-run health, install health, and pending governance events. - **Ecosystem hardening** — AgentShield, ECC Tools cost controls, billing portal work, and website refreshes continue to ship around the core plugin instead of drifting into separate silos. ### v1.9.0 — Selective Install & Language Expansion (Mar 2026) diff --git a/scripts/lib/state-store/queries.js b/scripts/lib/state-store/queries.js index 8c47455b..d13270a0 100644 --- a/scripts/lib/state-store/queries.js +++ b/scripts/lib/state-store/queries.js @@ -202,6 +202,22 @@ function summarizeInstallHealth(installations) { }; } +function summarizeReadiness({ activeSessionCount, skillRuns, installHealth, pendingGovernanceCount }) { + const failedSkillRuns = skillRuns.summary.failureCount; + const warningInstallations = installHealth.warningCount; + const pendingGovernanceEvents = pendingGovernanceCount; + const attentionCount = failedSkillRuns + warningInstallations + pendingGovernanceEvents; + + return { + status: attentionCount > 0 ? 'attention' : 'ok', + attentionCount, + activeSessions: activeSessionCount, + failedSkillRuns, + warningInstallations, + pendingGovernanceEvents, + }; +} + function normalizeSessionInput(session) { return { id: session.id, @@ -568,24 +584,34 @@ function createQueryApi(db) { const pendingLimit = normalizeLimit(options.pendingLimit, 5); const activeSessions = listActiveSessionsStatement.all(activeLimit).map(mapSessionRow); + const activeSessionCount = countActiveSessionsStatement.get().total_count; const recentSkillRuns = listRecentSkillRunsStatement.all(recentSkillRunLimit).map(mapSkillRunRow); const installations = listInstallStateStatement.all().map(mapInstallStateRow); const pendingGovernanceEvents = listPendingGovernanceStatement.all(pendingLimit).map(mapGovernanceEventRow); + const skillRuns = { + windowSize: recentSkillRunLimit, + summary: summarizeSkillRuns(recentSkillRuns), + recent: recentSkillRuns, + }; + const installHealth = summarizeInstallHealth(installations); + const pendingGovernanceCount = countPendingGovernanceStatement.get().total_count; return { generatedAt: new Date().toISOString(), + readiness: summarizeReadiness({ + activeSessionCount, + skillRuns, + installHealth, + pendingGovernanceCount, + }), activeSessions: { - activeCount: countActiveSessionsStatement.get().total_count, + activeCount: activeSessionCount, sessions: activeSessions, }, - skillRuns: { - windowSize: recentSkillRunLimit, - summary: summarizeSkillRuns(recentSkillRuns), - recent: recentSkillRuns, - }, - installHealth: summarizeInstallHealth(installations), + skillRuns, + installHealth, governance: { - pendingCount: countPendingGovernanceStatement.get().total_count, + pendingCount: pendingGovernanceCount, events: pendingGovernanceEvents, }, }; diff --git a/scripts/status.js b/scripts/status.js index ff23338c..ed248ac9 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -142,9 +142,20 @@ function printGovernance(section) { } } +function printReadiness(section) { + console.log(`Readiness: ${section.status}`); + console.log(` Attention items: ${section.attentionCount}`); + console.log(` Active sessions: ${section.activeSessions}`); + console.log(` Failed skill runs: ${section.failedSkillRuns}`); + console.log(` Warning installs: ${section.warningInstallations}`); + console.log(` Pending governance: ${section.pendingGovernanceEvents}`); +} + function printHuman(payload) { console.log('ECC status\n'); console.log(`Database: ${payload.dbPath}\n`); + printReadiness(payload.readiness); + console.log(); printActiveSessions(payload.activeSessions); console.log(); printSkillRuns(payload.skillRuns); @@ -169,6 +180,15 @@ function renderMarkdown(payload) { `Generated: ${payload.generatedAt}`, `Database: ${formatCode(payload.dbPath)}`, '', + '## Readiness', + '', + `Status: ${payload.readiness.status}`, + `Attention items: ${payload.readiness.attentionCount}`, + `Active sessions: ${payload.readiness.activeSessions}`, + `Failed skill runs: ${payload.readiness.failedSkillRuns}`, + `Warning installs: ${payload.readiness.warningInstallations}`, + `Pending governance: ${payload.readiness.pendingGovernanceEvents}`, + '', '## Active Sessions', '', `Active sessions: ${payload.activeSessions.activeCount}`, diff --git a/tests/lib/state-store.test.js b/tests/lib/state-store.test.js index b8a9b6d3..64c0b5fd 100644 --- a/tests/lib/state-store.test.js +++ b/tests/lib/state-store.test.js @@ -339,6 +339,12 @@ async function runTests() { const status = store.getStatus(); store.close(); + assert.strictEqual(status.readiness.status, 'attention'); + assert.strictEqual(status.readiness.attentionCount, 2); + assert.strictEqual(status.readiness.activeSessions, 1); + assert.strictEqual(status.readiness.failedSkillRuns, 1); + assert.strictEqual(status.readiness.warningInstallations, 0); + assert.strictEqual(status.readiness.pendingGovernanceEvents, 1); assert.strictEqual(status.activeSessions.activeCount, 1); assert.strictEqual(status.activeSessions.sessions[0].id, 'session-active'); assert.strictEqual(status.skillRuns.summary.totalCount, 4); @@ -365,6 +371,9 @@ async function runTests() { store.close(); assert.strictEqual(missingDetail, null); + assert.strictEqual(status.readiness.status, 'ok'); + assert.strictEqual(status.readiness.attentionCount, 0); + assert.strictEqual(status.readiness.activeSessions, 0); assert.strictEqual(status.activeSessions.activeCount, 0); assert.deepStrictEqual(status.activeSessions.sessions, []); assert.strictEqual(status.skillRuns.summary.totalCount, 0); @@ -562,11 +571,15 @@ async function runTests() { const jsonResult = runNode(STATUS_SCRIPT, ['--db', dbPath, '--json']); assert.strictEqual(jsonResult.status, 0, jsonResult.stderr); const jsonPayload = parseJson(jsonResult.stdout); + assert.strictEqual(jsonPayload.readiness.status, 'attention'); + assert.strictEqual(jsonPayload.readiness.attentionCount, 2); assert.strictEqual(jsonPayload.activeSessions.activeCount, 1); assert.strictEqual(jsonPayload.governance.pendingCount, 1); const humanResult = runNode(STATUS_SCRIPT, ['--db', dbPath]); assert.strictEqual(humanResult.status, 0, humanResult.stderr); + assert.match(humanResult.stdout, /Readiness: attention/); + assert.match(humanResult.stdout, /Attention items: 2/); assert.match(humanResult.stdout, /Active sessions: 1/); assert.match(humanResult.stdout, /Skill runs \(last 20\):/); assert.match(humanResult.stdout, /Install health: healthy/); @@ -592,6 +605,9 @@ async function runTests() { assert.strictEqual(result.stdout, written); assert.match(written, /^# ECC Status/m); assert.match(written, /Database: `[^`]+state\.db`/); + assert.match(written, /## Readiness/); + assert.match(written, /Status: attention/); + assert.match(written, /Attention items: 2/); assert.match(written, /- `session-active` \[claude\/dmux-tmux\] active/); assert.match(written, /Success rate: 66\.7%/); assert.match(written, /Install health: healthy/);