diff --git a/.github/workflows/loop-ready.yml b/.github/workflows/loop-ready.yml
new file mode 100644
index 0000000..4ab3fd3
--- /dev/null
+++ b/.github/workflows/loop-ready.yml
@@ -0,0 +1,46 @@
+name: loop-ready
+
+# The README's terminal demo is rendered from a REAL audit run, never hand-edited.
+# This workflow is the live half of that promise: every push re-runs the audit
+# (the badge in the README goes red if any kit fails) and re-renders
+# assets/terminal-loop-ready.svg from the actual results, committing it only
+# when the score actually changed.
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: write
+
+jobs:
+ audit-and-render:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Run the Loop Ready audit
+ run: npm run audit:loops
+
+ - name: Re-render the README terminal demo from the audit output
+ # Runs even when the audit fails, so the image shows the red run too.
+ if: always()
+ run: npm run render:terminal
+
+ - name: Commit the refreshed image (main only, when it changed)
+ if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'
+ run: |
+ if git diff --quiet assets/terminal-loop-ready.svg; then
+ echo "Terminal demo unchanged — nothing to commit."
+ exit 0
+ fi
+ git config user.name "loop-ready-bot"
+ git config user.email "actions@github.com"
+ git add assets/terminal-loop-ready.svg
+ git commit -m "chore(readme): re-render terminal demo from live audit"
+ git push
diff --git a/README.md b/README.md
index 40655e8..7ab8470 100644
--- a/README.md
+++ b/README.md
@@ -1,108 +1,279 @@
+
-
+
+
+
# Loop Engineering Crash Course
-**Learn to build AI agent loops — autonomous systems with a heartbeat, a spine, a
-checker, and a stop you can prove.**
+*Stop prompting one message at a time. Design the loop.*
+
+**Learn to build AI agent loops — autonomous systems with a heartbeat, a body, a
+checker, a spine, and a stop you can prove.**
-You will not read *about* loops here; you will build them. By the last page you will
-have designed, shipped, and safely operated real loops in two different tools — opening
-with a single in-session loop and closing with a certified multi-loop fleet. It is free,
-open source, and readable end to end on GitHub.
+
-[](LICENSE)
-[](docs/00-start-here/README.md)
-[](CONTRIBUTING.md)
-[](LOOP.md)
+The leverage has moved: it no longer lives in the perfect prompt, but in the **control
+system that keeps an agent working toward a goal over time**. This free, open-source
+course teaches that discipline end to end — 14 steps, 20 ready-to-run loop kits, graded
+labs, an operating handbook, and a certification. You don't read *about* loops here;
+you build them, in two tools, from a first in-session loop to a certified fleet.
+
+
+
+
+
+*Real command, real score — this image is
+[rendered from an actual run](scripts/render-loop-ready-terminal.mjs) of the
+deterministic [Loop Ready audit](scripts/loop-ready-audit.mjs), and
+[CI re-renders it](.github/workflows/loop-ready.yml) on every push. If a kit ever
+fails, this terminal goes red.*
+
+
+
+Want a loop running in your own project before the first lesson? One command:
+
+```bash
+# see the 20 ready-made loop kits
+npx @loop-engineering/loop-kit list
-Loop Engineering is the fast-emerging craft of **designing the system that prompts your
-AI agents** instead of prompting them one message at a time. The leverage has moved: it
-no longer lives in the perfect prompt, but in the control system that keeps an agent
-working toward a goal over time. This repository packages that whole discipline — the
-concepts, the mechanics, a library of ready-to-run loops, hands-on labs, and the safety
-guidance that keeps you in charge — as a complete course you can read start to finish on
-GitHub. The same `docs/` content powers the course website too, so the two surfaces can
-never drift apart (one source of truth, always).
+# install one into the project you want the loop to watch
+npx @loop-engineering/loop-kit ci-sweeper
+```
> **We eat our own cooking.** This is a course about loops that is *built by loops*. The
> rulebook lives in [`LOOP.md`](LOOP.md), the durable spine in [`STATE.md`](STATE.md),
> and every single run is logged, one line at a time, in
> [`shared/loop-run-log.md`](shared/loop-run-log.md).
-## Start here
+## 📌 Quick links
-**New? Spend 60 seconds on the router before anything else:** →
-[`docs/00-start-here/README.md`](docs/00-start-here/README.md)
+| I want to… | |
+| --- | --- |
+| **Enroll** — find my starting point in 60 seconds | [**View →**](docs/00-start-here/README.md) |
+| See the full learning-track map with entry checks | [**View →**](docs/00-start-here/learning-tracks.md) |
+| Set up my agent (Claude Code, OpenCode, Codex, Grok) | [**View →**](docs/01-prerequisites/environment-setup.md) |
+| Look up a term in the glossary | [**View →**](docs/02-foundations/glossary.md) |
+| Install a ready-made loop in my own project | [**View →**](starters/getting-started.md) |
+| Browse the 20 loop patterns | [**View →**](#-loop-pattern-library) |
+| Operate loops safely (failure modes, recovery) | [**View →**](docs/10-operating/operating-loops.md) |
+| Get certified | [**View →**](docs/assessments/loop-ready-certification.md) |
-Answer four honest questions and it drops you onto exactly one of four tracks — so a
-first-timer never drowns and a veteran never yawns:
+
+
+## 🎓 Choose your track
+
+**New here?** [**Open the 60-second router →**](docs/00-start-here/README.md)
+
+Like any good LMS, this course meets you where you are. Answer four honest questions and
+the router drops you onto exactly one of four tracks — so a first-timer never drowns and
+a veteran never yawns:
| Track | You are… | You'll learn to… |
-| ------- | ---------- | ------------------ |
+| --- | --- | --- |
| **T1 · Foundations** | using an AI agent by hand (or not yet) | explain the shift and run your first in-session loop |
| **T2 · Practitioner** | fluent in the six parts of a loop | choose a heartbeat, write a provable stop, split maker/checker |
| **T3 · Engineer** | able to assemble a loop | build the full six-part loop in two tools and operate it safely |
| **T4 · Ultra-Pro** | shipping loops already | hill-climbing loops, fleets, governance at team scale |
-Full map with entry checks and exit assessments:
-[`docs/00-start-here/learning-tracks.md`](docs/00-start-here/learning-tracks.md)
+[**View the full track map with entry checks and exit assessments →**](docs/00-start-here/learning-tracks.md)
+
+
+
+## 📚 Curriculum
+
+**Prerequisites & foundations (read first):**
+[Environment Setup](docs/01-prerequisites/environment-setup.md) ·
+[Agentic-Coding Primer](docs/01-prerequisites/agentic-coding-primer.md) ·
+[Spec-Driven Primer](docs/01-prerequisites/spec-driven-primer.md) ·
+[Mental Models](docs/02-foundations/mental-models.md) ·
+[Concepts](docs/02-foundations/concepts.md) ·
+[The Four Layers](docs/02-foundations/the-four-layers.md) ·
+[Primitives](docs/02-foundations/primitives.md) ·
+[Primitives Matrix](docs/02-foundations/primitives-matrix.md) ·
+[Glossary](docs/02-foundations/glossary.md)
+
+**The 14-step core roadmap** — six parts, each ending in something you built:
+
+| Part | Steps | What you learn | |
+| --- | --- | --- | --- |
+| **1 · The Shift** | 01–03 | prompting → looping, the four layers, anatomy of a loop | [**Start →**](docs/03-part-1-the-shift/) |
+| **2 · The Heartbeat** | 04–07 | in-session, run-until-done, schedules, event-driven | [**Start →**](docs/04-part-2-heartbeat/) |
+| **3 · The Body** | 08–11 | worktrees, skills, connectors/MCP, maker ≠ checker | [**Start →**](docs/05-part-3-the-body/) |
+| **4 · The Spine** | 12 | durable state between runs | [**Start →**](docs/06-part-4-the-spine/) |
+| **5 · A Complete Loop** | 13 | build the same loop twice (Claude Code & OpenCode) | [**Start →**](docs/07-part-5-complete-loop/) |
+| **6 · Human Control** | 14 | staying the engineer: cost, verification, nested loops | [**Start →**](docs/08-part-6-human-control/) |
+
+**Then keep climbing:**
+
+| Stage | What it holds | |
+| --- | --- | --- |
+| **Methods** | design your own loop: [Decision Framework](docs/09-methods/decision-framework.md), [Design Checklist](docs/09-methods/loop-design-checklist.md), [Pattern Picker](docs/09-methods/pattern-picker.md), [Worked Example](docs/09-methods/worked-example-dependency-sweeper.md) | [**View →**](docs/09-methods/make-your-own-loop.md) |
+| **Operating handbook** | safety, failure modes, infinite loops, observability, recovery | [**View →**](docs/10-operating/operating-loops.md) |
+| **Graded labs** | 11 hands-on projects, from a watch loop to a two-routine gate — with [solutions](docs/projects/solutions/) | [**View →**](docs/projects/) |
+| **Advanced** | hill-climbing, multi-loop coordination, evals & traces, governance, enterprise scale | [**View →**](docs/advanced/) |
+| **Certification** | the **Loop Ready** capstone: [Final Exam](docs/assessments/final-exam.md) · [Capstone Rubric](docs/assessments/capstone-rubric.md) | [**View →**](docs/assessments/loop-ready-certification.md) |
+
+Every lesson shows the same build in at least two tools, side by side
+(Claude Code ↔ OpenCode), so you learn the discipline — not one vendor's syntax.
+
+
-## Quickstart
+## 🔁 Anatomy of a loop
-1. **Set up your agent** — [`docs/01-prerequisites/environment-setup.md`](docs/01-prerequisites/environment-setup.md)
+Six parts, one discipline. A **heartbeat** decides when the agent runs, a **body** does
+the work, a **checker** grades it (the maker never grades its own work), a **spine**
+remembers across runs, a **provable stop** ends it, and **you** stay the engineer:
+
+```mermaid
+flowchart LR
+ H(["⏱ Heartbeat when it runs"]) --> B["🦾 Body does the work"]
+ B --> C{"✅ Checker grades it"}
+ C -- "not done" --> S[("🧠 Spine remembers")]
+ S --> H
+ C -- "success · run limit · no progress" --> X(["🛑 Provable stop"])
+ X --> U["🧑💻 You — the engineer"]
+```
+
+Start with [Mental Models](docs/02-foundations/mental-models.md) and
+[The Four Layers](docs/02-foundations/the-four-layers.md); keep the
+[Glossary](docs/02-foundations/glossary.md) open in a tab.
+
+
+
+## 🧰 Loop pattern library
+
+Twenty production-shaped loop kits, each with a definition, a spine, a budget, a
+constitution, and a read-only checker. Install any of them into your own project with
+one command — no clone required:
+
+```bash
+npx @loop-engineering/loop-kit
+```
+
+| Pattern | What it does |
+| --- | --- |
+| [`ci-sweeper`](patterns/ci-sweeper.md) | fires the instant CI goes red on `main` and sweeps it back to green |
+| [`pr-babysitter`](patterns/pr-babysitter.md) | keeps open PRs from rotting — conflicts, red CI, silent reviewers |
+| [`daily-triage`](patterns/daily-triage.md) | clears the overnight noise before you open your laptop |
+| [`issue-triage`](patterns/issue-triage.md) | `daily-triage`'s narrower cousin, stripped down to issues alone |
+| [`dependency-sweeper`](patterns/dependency-sweeper.md) | keeps dependencies current without updating them blind |
+| [`dependency-cve-burndown`](patterns/dependency-cve-burndown.md) | reads the 3 a.m. CVE advisory so it never waits until Monday |
+| [`docs-sweep`](patterns/docs-sweep.md) | catches documentation the moment the code moves on without it |
+| [`changelog-drafter`](patterns/changelog-drafter.md) | drafts the changelog your busy sprint was going to forget |
+| [`test-coverage-loop`](patterns/test-coverage-loop.md) | burns down the "we should really test that" backlog |
+| [`test-stabilizer-loop`](patterns/test-stabilizer-loop.md) | hunts flaky tests until a red run means something again |
+| [`prod-error-sweep`](patterns/prod-error-sweep.md) | sweeps the production errors that actually cost users something |
+| [`page-load-loop`](patterns/page-load-loop.md) | keeps pushing page-load performance downhill, beat after beat |
+| [`repo-cleanup-loop`](patterns/repo-cleanup-loop.md) | clears repository clutter — branches nobody deleted, and worse |
+| [`post-merge-cleanup`](patterns/post-merge-cleanup.md) | finishes the story every merge leaves behind |
+| [`ticket-to-pr-ready`](patterns/ticket-to-pr-ready.md) | one ticket, one run: reproduce, fix, arrive PR-ready |
+| [`spec-dev-review`](patterns/spec-dev-review.md) | one story → a scoped spec packet: findings, progress, review |
+| [`clodex-adversarial-review`](patterns/clodex-adversarial-review.md) | a real maker–checker pair: Claude implements, Codex reviews |
+| [`codex-completion-contract`](patterns/codex-completion-contract.md) | stops "mostly done" from quietly passing as "done" |
+| [`loop-harness-verification`](patterns/loop-harness-verification.md) | a general-purpose harness for any scheduled repo task |
+| [`stale-safe-batch-release`](patterns/stale-safe-batch-release.md) | the bookend: batches what every other kit left safely releasable |
+
+Prefer to design your own from a blank? `npx @loop-engineering/loop-kit new `
+scaffolds one from [the blank template](starters/_template/README.md).
+[**View the full install guide →**](starters/getting-started.md)
+
+
+
+## 🚀 Getting started (5 minutes)
+
+1. **Enroll** — run the [60-second router](docs/00-start-here/README.md). It hands you
+ a track.
+2. **Set up your agent** — follow the
+ [Environment Setup guide](docs/01-prerequisites/environment-setup.md)
(Claude Code, OpenCode, Codex, or Grok; every lesson shows at least Claude Code ↔ OpenCode).
-2. **Speed-run the primers** — [`agentic-coding-primer.md`](docs/01-prerequisites/agentic-coding-primer.md)
- and [`spec-driven-primer.md`](docs/01-prerequisites/spec-driven-primer.md).
-3. **Ground yourself in the foundations** — start with
- [`mental-models.md`](docs/02-foundations/mental-models.md), keep the
- [`glossary.md`](docs/02-foundations/glossary.md) open in a tab.
-4. **Begin the 14 steps** (Part 1 opens Day 2 — see the roadmap below).
-
-## The course at a glance
-
-**Foundations (read first):**
-[glossary](docs/02-foundations/glossary.md) ·
-[mental models](docs/02-foundations/mental-models.md) ·
-[concepts](docs/02-foundations/concepts.md) ·
-[the four layers](docs/02-foundations/the-four-layers.md) ·
-[primitives](docs/02-foundations/primitives.md) ·
-[primitives matrix](docs/02-foundations/primitives-matrix.md)
-
-**The 14-step roadmap** *(content lands over the next days — links activate as parts ship)*:
-
-| Part | Steps | What you learn |
-| ------ | ------- | ---------------- |
-| 1 · The Shift | 01–03 | prompting → looping, the four layers, anatomy of a loop |
-| 2 · The Heartbeat | 04–07 | in-session, run-until-done, schedules, event-driven |
-| 3 · The Body | 08–11 | worktrees, skills, connectors/MCP, maker ≠ checker |
-| 4 · The Spine | 12 | durable state between runs |
-| 5 · A Complete Loop | 13 | build the same loop twice (Claude Code & OpenCode) |
-| 6 · Human Control | 14 | staying the engineer: cost, verification, nested loops |
-
-Then: methods for designing your own loop, the operating handbook (safety, failure
-modes, recovery), the prebuilt loop library, graded labs, and the **Loop Ready**
-certification capstone.
-
-## What's in the repo
+3. **Speed-run the primers** — the
+ [Agentic-Coding Primer](docs/01-prerequisites/agentic-coding-primer.md) and the
+ [Spec-Driven Primer](docs/01-prerequisites/spec-driven-primer.md).
+4. **Ground yourself in the foundations** — start with
+ [Mental Models](docs/02-foundations/mental-models.md).
+5. **Begin the 14 steps** — [Part 1 · The Shift](docs/03-part-1-the-shift/), then follow
+ the [curriculum](#-curriculum) through to the
+ [Loop Ready certification](docs/assessments/loop-ready-certification.md).
+
+
+
+## 🛡️ Operating & safety
+
+A loop without a provable stop is an incident with a delay. The operating handbook is a
+first-class part of the course, not an appendix:
+
+- [Operating loops](docs/10-operating/operating-loops.md) — the day-to-day handbook
+- [Safety](docs/10-operating/safety.md) — L1 report-only first, human gates, budgets
+- [Failure modes](docs/10-operating/failure-modes.md) ·
+ [Infinite loops](docs/10-operating/infinite-loops.md) ·
+ [Anti-patterns](docs/10-operating/anti-patterns.md)
+- [Observability](docs/10-operating/observability.md) ·
+ [Recovery playbook](docs/10-operating/recovery-playbook.md) ·
+ [Multi-loop operation](docs/10-operating/multi-loop.md)
+
+The house rules this repo itself runs under — one owner per file, a sacred spine, stop
+conditions as specs, green ≠ done — live in [`LOOP.md`](LOOP.md).
+
+## 🏅 Certification: Loop Ready
+
+The course ends with a graded capstone, not a participation badge. You design, build,
+and operate a loop of your own, then defend it against the
+[Capstone Rubric](docs/assessments/capstone-rubric.md) and pass the
+[Final Exam](docs/assessments/final-exam.md).
+[**View the full certification path →**](docs/assessments/loop-ready-certification.md)
+
+
+
+## 📂 Repository map
| Folder | What it holds |
-| -------- | --------------- |
+| --- | --- |
| `docs/` | the course — single source of truth for GitHub *and* the website |
| `loops/` | [the loops that build this course](loops/README.md) — real prompts, real spines |
-| `patterns/` | the seven core loop patterns *(Day 2+)* |
+| `patterns/` | the 20 loop patterns ([table above](#-loop-pattern-library)) |
| `starters/` | [install-and-fill loop kits](starters/README.md) — [one `npx` command](starters/getting-started.md) into any project, or start from [`_template/`](starters/_template/README.md) |
-| `skills/`, `templates/`, `examples/`, `stories/` | reusable parts and case studies *(Day 2+)* |
+| `packages/loop-kit/` | the published CLI behind `npx @loop-engineering/loop-kit` |
+| `skills/`, `templates/`, `examples/`, `stories/` | reusable parts and case studies |
| `resources/` | [source attribution](resources/sources.md) for all nine primary sources |
-| `web/` | the Next.js course website *(Day 4)* |
+| `web/` | the Next.js course website *(in progress)* |
+
+## 🤝 Contributing & credits
+
+Pull requests are genuinely welcome — start with the
+[Contributing Guide](CONTRIBUTING.md). This course stands on the shoulders of nine
+primary sources: it synthesizes and adapts MIT-licensed material and public writing,
+and it credits every one of them, page by page, on the
+[Sources & Attribution page](resources/sources.md). Academic or written citation?
+Use [`CITATION.cff`](CITATION.cff).
+
+Licensed [MIT](LICENSE) — free to learn from, fork, and teach with.
+
+
-## Contributing & credits
+**[⬆ Back to top](#loop-engineering-crash-course)** · Built with its own loops, one
+provable stop at a time.
-Pull requests are genuinely welcome — start with [`CONTRIBUTING.md`](CONTRIBUTING.md).
-This course stands on the shoulders of nine primary sources: it synthesizes and adapts
-MIT-licensed material and public writing, and it credits every one of them, page by page,
-in [`resources/sources.md`](resources/sources.md). Licensed [MIT](LICENSE).
+
diff --git a/assets/hero-banner.svg b/assets/hero-banner.svg
new file mode 100644
index 0000000..dc3dd01
--- /dev/null
+++ b/assets/hero-banner.svg
@@ -0,0 +1,141 @@
+
diff --git a/assets/terminal-loop-ready.svg b/assets/terminal-loop-ready.svg
new file mode 100644
index 0000000..06ae2c0
--- /dev/null
+++ b/assets/terminal-loop-ready.svg
@@ -0,0 +1,220 @@
+
diff --git a/package.json b/package.json
index 06ac018..c0badc9 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"scripts": {
"validate:registry": "node scripts/validate-registry.mjs",
"audit:loops": "node scripts/loop-ready-audit.mjs",
+ "render:terminal": "node scripts/render-loop-ready-terminal.mjs",
"bundle:kit": "node packages/loop-kit/scripts/bundle-kits.mjs"
}
}
diff --git a/scripts/loop-ready-audit.mjs b/scripts/loop-ready-audit.mjs
index 635ce7f..ff9c778 100644
--- a/scripts/loop-ready-audit.mjs
+++ b/scripts/loop-ready-audit.mjs
@@ -82,20 +82,40 @@ function auditKit(name) {
}
const kits = listKits();
+// --json emits machine-readable results (used by render-loop-ready-terminal.mjs
+// to draw the README demo from real data); human output is unchanged without it.
+const jsonMode = process.argv.includes("--json");
if (kits.length === 0) {
- console.log("No kits found in starters/ yet (only _template/ exists). Nothing to audit.");
+ if (jsonMode) {
+ console.log(JSON.stringify({ total: 0, passed: 0, kits: [] }));
+ } else {
+ console.log("No kits found in starters/ yet (only _template/ exists). Nothing to audit.");
+ }
process.exit(0);
}
let anyFail = false;
+const results = [];
for (const name of kits) {
const result = auditKit(name);
+ results.push(result);
if (!result.pass) anyFail = true;
+ if (jsonMode) continue;
console.log(`\n${result.pass ? "PASS" : "FAIL"} — ${name}`);
for (const m of result.missing) console.log(` missing: ${m}`);
for (const p of result.placeholders) console.log(` unfilled placeholder in ${p}`);
}
-console.log(`\n${kits.length} kit(s) checked. ${anyFail ? "At least one FAIL." : "All PASS."}`);
+if (jsonMode) {
+ console.log(
+ JSON.stringify(
+ { total: kits.length, passed: results.filter((r) => r.pass).length, kits: results },
+ null,
+ 2
+ )
+ );
+} else {
+ console.log(`\n${kits.length} kit(s) checked. ${anyFail ? "At least one FAIL." : "All PASS."}`);
+}
process.exit(anyFail ? 1 : 0);
diff --git a/scripts/render-loop-ready-terminal.mjs b/scripts/render-loop-ready-terminal.mjs
new file mode 100644
index 0000000..fad3b34
--- /dev/null
+++ b/scripts/render-loop-ready-terminal.mjs
@@ -0,0 +1,261 @@
+#!/usr/bin/env node
+// Renders assets/terminal-loop-ready.svg from a REAL run of the Loop Ready audit.
+// The README terminal demo is never hand-edited: this script runs
+// scripts/loop-ready-audit.mjs --json, groups the actual results into the ten
+// checks every kit must pass, and draws exactly what the audit said — ten
+// streaming check rows, a score ring that climbs as each check lands, and the
+// real exit code. Ten checks × 10 points = the Loop Ready score out of 100.
+// Usage: node scripts/render-loop-ready-terminal.mjs (or: npm run render:terminal)
+
+import { spawnSync } from "node:child_process";
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, "..");
+const outFile = path.join(repoRoot, "assets", "terminal-loop-ready.svg");
+
+// ── 1. Run the real audit ────────────────────────────────────────────────────
+const audit = spawnSync("node", [path.join(__dirname, "loop-ready-audit.mjs"), "--json"], {
+ cwd: repoRoot,
+ encoding: "utf8",
+});
+const exitCode = audit.status ?? 1;
+
+let data;
+try {
+ data = JSON.parse(audit.stdout);
+} catch {
+ console.error("render-terminal: could not parse audit --json output.");
+ process.exit(1);
+}
+const { total, passed, kits } = data;
+if (total === 0) {
+ console.error("render-terminal: no kits to audit — nothing to render.");
+ process.exit(1);
+}
+const allPass = passed === total;
+
+// ── 2. Group the real results into the ten checks every kit must pass ───────
+// Each check is worth 10 points; a check's points scale with how many kits pass
+// it, so the score is 100 exactly when every kit passes every check.
+const missing = (kit, file) => kit.missing.includes(file);
+const CHECKS = [
+ { label: "definition", path: "LOOP.md", fails: (k) => missing(k, "LOOP.md") },
+ { label: "docs", path: "README.md", fails: (k) => missing(k, "README.md") },
+ { label: "spine", path: "-state.md", fails: (k) => missing(k, `${k.name}-state.md`) },
+ { label: "budget", path: "loop-budget.md", fails: (k) => missing(k, "loop-budget.md") },
+ { label: "constitution", path: "loop-constraints.md", fails: (k) => missing(k, "loop-constraints.md") },
+ { label: "run log", path: "loop-run-log.md", fails: (k) => missing(k, "loop-run-log.md") },
+ { label: "checker", path: ".claude/agents/loop-verifier.md", fails: (k) => missing(k, ".claude/agents/loop-verifier.md") },
+ { label: "skill", path: ".claude/skills/loop-task/SKILL.md", fails: (k) => missing(k, ".claude/skills/loop-task/SKILL.md") },
+ { label: "dual tool", path: "opencode.json + loop-task.md", fails: (k) => missing(k, "opencode.json.example") || missing(k, "skills/loop-task.md") },
+ { label: "filled in", path: "no unfilled template placeholders", fails: (k) => k.placeholders.length > 0 },
+];
+
+const rows = CHECKS.map((c) => {
+ const ok = kits.filter((k) => !c.fails(k)).length;
+ return { ...c, ok, points: (ok / total) * 10 };
+});
+
+// Cumulative score after each row lands — these drive the climbing counter.
+// The final score floors instead of rounding so a single failing kit can never
+// round back up to a perfect 100 (epsilon guards float sums like 99.9999…).
+const steps = [0];
+for (const r of rows) steps.push(steps[steps.length - 1] + r.points);
+const score = Math.floor(steps[steps.length - 1] + 1e-9);
+const scoreValues = steps.map((s, i) => (i === steps.length - 1 ? score : Math.floor(s + 1e-9)));
+
+const scoreColor = score === 100 ? "#34d399" : score >= 70 ? "#fbbf24" : "#f87171";
+
+// ── 3. Timeline ──────────────────────────────────────────────────────────────
+const T = 7.0; // total keyframe timeline in seconds
+const ROW_START = 1.9;
+const ROW_STEP = 0.32;
+const rowDelay = (i) => ROW_START + i * ROW_STEP;
+const lastRow = rowDelay(rows.length - 1);
+const summaryDelay = lastRow + 0.7;
+const badgeDelay = summaryDelay + 0.3;
+const pct = (t) => ((t / T) * 100).toFixed(2);
+
+// Climbing counter: value i is visible from rowDelay(i-1)+0.15 until the next
+// value takes over; the final value stays.
+const numCss = scoreValues
+ .map((v, i) => {
+ const from = i === 0 ? 0.4 : rowDelay(i - 1) + 0.15;
+ const isLast = i === scoreValues.length - 1;
+ const to = isLast ? T : rowDelay(i) + 0.15;
+ const kf = isLast
+ ? `@keyframes n${i} { 0%, ${pct(from)}% { opacity: 0; } ${pct(from + 0.02)}%, 100% { opacity: 1; } }`
+ : `@keyframes n${i} { 0%, ${pct(from)}% { opacity: 0; } ${pct(from + 0.02)}%, ${pct(to)}% { opacity: 1; } ${pct(to + 0.02)}%, 100% { opacity: 0; } }`;
+ return ` .n${i} { opacity: 0; animation: n${i} ${T}s linear forwards; }\n ${kf}`;
+ })
+ .join("\n");
+
+const rowCss = rows
+ .map((_, i) => ` .r${i} { animation-delay: ${rowDelay(i).toFixed(2)}s; }`)
+ .join("\n");
+
+// ── 4. Geometry ──────────────────────────────────────────────────────────────
+const ROW_H = 30;
+const rowsTop = 150;
+const summaryY = rowsTop + rows.length * ROW_H + 26;
+const height = summaryY + 50;
+const RING = { cx: 726, cy: 278, r: 84 };
+const circ = 2 * Math.PI * RING.r;
+const ringTarget = (circ * (1 - score / 100)).toFixed(1);
+
+const esc = (s) => s.replace(/&/g, "&").replace(//g, ">");
+
+const rowSvg = rows
+ .map((r, i) => {
+ const y = rowsTop + i * ROW_H;
+ const good = r.ok === total;
+ const mark = good ? "✔" : "✖";
+ const markCls = good ? "green" : "red";
+ return `
+ ${mark}
+ ${esc(r.label)}
+ ${esc(r.path)}
+ ${r.ok}/${total}
+ `;
+ })
+ .join("\n");
+
+const summary = allPass
+ ? `✔ ${passed} / ${total} kits Loop Ready·exit ${exitCode}`
+ : `✖ ${passed} / ${total} kits Loop Ready·exit ${exitCode}`;
+
+const numSvg = scoreValues
+ .map((v, i) => ` ${v}`)
+ .join("\n");
+
+// ── 5. Draw ──────────────────────────────────────────────────────────────────
+const svg = `
+`;
+
+fs.writeFileSync(outFile, svg);
+console.log(
+ `render-terminal: score ${score}/100 — ${passed}/${total} kits pass — wrote ${path.relative(repoRoot, outFile)}`
+);