diff --git a/README.md b/README.md index ff547c61..618a3d7d 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 readiness, active sessions, skill-run health, install health, pending governance events, and linked work items from Linear/GitHub/handoffs. Use `ecc work-items upsert ...` to add or update those linked work items from the CLI. +- **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, pending governance events, and linked work items from Linear/GitHub/handoffs. Use `ecc work-items upsert ...` to add or update those linked work items from the CLI, and `ecc status --exit-code` to fail automation when readiness needs attention. - **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/ecc.js b/scripts/ecc.js index fecfadea..ce15736a 100755 --- a/scripts/ecc.js +++ b/scripts/ecc.js @@ -113,6 +113,7 @@ Examples: ecc repair --dry-run ecc auto-update --dry-run ecc status --json + ecc status --exit-code ecc status --markdown --write status.md ecc sessions ecc sessions session-active --json diff --git a/scripts/status.js b/scripts/status.js index c6c9514a..e97b8d38 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -8,10 +8,12 @@ const { createStateStore } = require('./lib/state-store'); function showHelp(exitCode = 0) { console.log(` -Usage: node scripts/status.js [--db ] [--json|--markdown] [--write ] [--limit ] +Usage: node scripts/status.js [--db ] [--json|--markdown] [--write ] [--limit ] [--exit-code] Query the ECC SQLite state store for active sessions, recent skill runs, install health, pending governance events, and linked work items. + +Use --exit-code to return 2 when readiness needs attention. `); process.exit(exitCode); } @@ -23,6 +25,7 @@ function parseArgs(argv) { json: false, markdown: false, writePath: null, + exitCode: false, help: false, limit: 5, }; @@ -37,6 +40,8 @@ function parseArgs(argv) { parsed.json = true; } else if (arg === '--markdown') { parsed.markdown = true; + } else if (arg === '--exit-code') { + parsed.exitCode = true; } else if (arg === '--write') { parsed.writePath = args[index + 1] || null; index += 1; @@ -364,6 +369,10 @@ async function main() { } printHuman(payload); } + + if (options.exitCode && payload.readiness.status !== 'ok') { + process.exitCode = 2; + } } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); diff --git a/tests/lib/state-store.test.js b/tests/lib/state-store.test.js index 9c9ee940..79fea25b 100644 --- a/tests/lib/state-store.test.js +++ b/tests/lib/state-store.test.js @@ -667,6 +667,34 @@ async function runTests() { } })) passed += 1; else failed += 1; + if (await test('status CLI --exit-code reports attention without suppressing output', async () => { + const attentionDir = createTempDir('ecc-state-attention-'); + const okDir = createTempDir('ecc-state-ok-'); + const attentionDbPath = path.join(attentionDir, 'state.db'); + const okDbPath = path.join(okDir, 'state.db'); + + try { + await seedStore(attentionDbPath); + + const attentionResult = runNode(STATUS_SCRIPT, ['--db', attentionDbPath, '--json', '--exit-code']); + assert.strictEqual(attentionResult.status, 2, attentionResult.stderr); + const attentionPayload = parseJson(attentionResult.stdout); + assert.strictEqual(attentionPayload.readiness.status, 'attention'); + assert.strictEqual(attentionPayload.readiness.attentionCount, 2); + + const okStore = await createStateStore({ dbPath: okDbPath }); + okStore.close(); + + const okResult = runNode(STATUS_SCRIPT, ['--db', okDbPath, '--json', '--exit-code']); + assert.strictEqual(okResult.status, 0, okResult.stderr); + const okPayload = parseJson(okResult.stdout); + assert.strictEqual(okPayload.readiness.status, 'ok'); + } finally { + cleanupTempDir(attentionDir); + cleanupTempDir(okDir); + } + })) passed += 1; else failed += 1; + if (await test('status CLI can emit and write markdown operator snapshots', async () => { const testDir = createTempDir('ecc-state-cli-'); const dbPath = path.join(testDir, 'state.db');