done-done-axi 0.6.11 → 0.6.13

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/README.md CHANGED
@@ -3,19 +3,19 @@
3
3
 
4
4
  <img src="docs/logo.svg" width="64" height="64" alt="done-done">
5
5
 
6
- [![npm](https://img.shields.io/npm/v/done-done-axi?label=done-done&color=5754d7)](https://www.npmjs.com/package/done-done-axi) [![Node 22.18+](https://img.shields.io/badge/node-22.18%2B-3fb950)](https://nodejs.org) [![Early preview](https://img.shields.io/badge/status-preview-d29922)](#whats-next)
7
-
8
- </div>
9
-
10
- # Prove your app really works.
6
+ <h1>Prove your app really works.</h1>
11
7
 
12
- A command-line check of real screenshots, recordings and output.
8
+ A command-line check that drives your app the way a user does, then has a separate agent judge what it captured.
13
9
 
14
- For developers using coding agents, alone or in a [Firstmate](#use-with-firstmate-or-another-agent-fleet) fleet. Tests can pass while the app is still broken.
10
+ For developers using coding agents: the tests pass, the agent says "done", and the app is still broken.
15
11
 
16
- <img src="docs/media/demo.png" width="320" alt="Real sample app: Release checklist shows Capture the real app checked and Done. The actual claude-haiku-5-5 judge passes that captured screenshot at sample commit 08d635e46a98.">
12
+ <picture>
13
+ <source media="(max-width: 600px) and (prefers-color-scheme: dark)" srcset="docs/media/hero-phone-dark.png">
14
+ <source media="(max-width: 600px)" srcset="docs/media/hero-phone-light.png">
15
+ <img src="docs/media/hero.gif" width="880" alt="A real run: done-done run records a capture for commit ca78363, done-done check passes with the judge's reason, done-done receipt writes the receipt, and done-done init refuses a journey that only runs npm test.">
16
+ </picture>
17
17
 
18
- <sub>Real sample app + live judge verdict, at the captured commit.</sub>
18
+ <sub>A real 0.6.12 run on a small sample app: a capture, the judge's pass and its reason, the receipt, and a test-only journey refused. Long paths shortened; [still, light](docs/media/hero-light.png) · [dark](docs/media/hero-dark.png).</sub>
19
19
 
20
20
  ```sh
21
21
  npm install -g done-done-axi
@@ -23,41 +23,50 @@ cd your-app && done-done init
23
23
  done-done verify --outcome "<what works now>"
24
24
  ```
25
25
 
26
- The last line is your first result: your agent drives the real app, a judge checks each capture, and you get a pass with a receipt, or the judge's reason it did not pass.
26
+ The last line is your first result: your agent drives the real app, a fresh judge checks each capture, and you get a pass with a receipt, or the judge's reason it did not pass.
27
27
 
28
- **With or without Firstmate.** On its own, you or your agent run these three lines. In a [Firstmate](https://github.com/kunchenguid/firstmate) fleet, two files make every worker prove its task, plus an optional merge gate that checks that proof ([how](#use-with-firstmate-or-another-agent-fleet)).
28
+ **With or without Firstmate.** Alone, you or your agent run these three lines; nothing else is needed. Inside a [Firstmate](https://github.com/kunchenguid/firstmate) fleet, two small files make every worker prove its task and let the merge check that proof ([how](#inside-firstmate-or-another-agent-fleet)).
29
29
 
30
- [Website](https://umeranjum17.github.io/done-done-axi/) · [Setup](#install-in-3-steps) · [Agent skill](#use-it-from-your-agent) · [Reference](docs/reference.md) · [npm](https://www.npmjs.com/package/done-done-axi)
30
+ [Website](https://umeranjum17.github.io/done-done-axi/) · [Quick start](#quick-start) · [Why](#why-not-just-green-tests) · [Reference](docs/reference.md) · [npm](https://www.npmjs.com/package/done-done-axi)
31
31
 
32
- ## Install in 3 steps
33
-
34
- You need **[Node 22.18+](https://nodejs.org) and git**, plus your own signed-in coding agent or API key for the judge. Early preview; see [what's next](#whats-next).
32
+ [![npm](https://img.shields.io/npm/v/done-done-axi?label=done-done&color=5754d7)](https://www.npmjs.com/package/done-done-axi) [![Node 22.18+](https://img.shields.io/badge/node-22.18%2B-3fb950)](https://nodejs.org) [![Early preview](https://img.shields.io/badge/status-preview-d29922)](#whats-next)
35
33
 
36
- 1. **Install.** The first line above puts `done-done` (and the long name `done-done-axi`) on your PATH. No `sudo`? Add `--prefix ~/.local` and put `~/.local/bin` on your `PATH`.
37
- 2. **Set up your repo.** `init` asks you, in a terminal, which coding agent drives your app and which judge checks it. done-done never picks either for you. It then writes a minimal `.done-done.yaml` and a verify skill: how to launch and drive your app. Outside a terminal, pass `--agent <agent> --model <model> --judge <agent> --judge-model <model>` instead.
38
- 3. **Get your first result.** Say what works now, in plain words. `verify` has your agent drive the real product the way a user does and save screenshots or output. A fresh judge checks whether each capture shows your claim. A pass names the capture and writes a receipt for this commit. Anything else stops at a gate with the judge's reason, and `done-done respond` answers it. No judge pass, no receipt.
34
+ </div>
39
35
 
40
- `done-done --version` prints the bare version first; help headers show it too, and suffixed versions are labelled `pre-release`.
36
+ ## Quick start
41
37
 
42
- Optional setup check: `done-done doctor --probe` requires a real answer from your chosen agent; it checks setup, not whether your app works.
38
+ You need **[Node 22.18+](https://nodejs.org) and git**, plus your own signed-in coding agent or API key for the judge.
43
39
 
44
- For a signed-in Claude Code setup, pass `--agent claude --model claude-haiku-5-5 --judge claude --judge-model claude-haiku-5-5` to `init`. For Codex, use `--agent codex --model gpt-6.1-sol` with that same judge. Codex readers and probes require a CLI with `exec --ignore-user-config` and `debug models --bundled`: they keep your login, but disable MCP, project instructions and model-provided tools; edit/drive steps keep your normal Codex config. Codex is not a judge route.
40
+ 1. **Install.** The first line above puts `done-done` and `done-done-axi` on your PATH. No `sudo`? Add `--prefix ~/.local` and put `~/.local/bin` on your `PATH`.
41
+ 2. **Set up your repo.** `init` asks which coding agent drives your app and which judge checks it, and never picks either for you. It writes a minimal `.done-done.yaml` and a verify skill that says how to launch and drive your app. Outside a terminal, pass `--agent <agent> --model <model> --judge <agent> --judge-model <model>`.
42
+ 3. **Get your first result.** Say what works now, in plain words. Your agent drives the real product as a user would and saves screenshots, a recording or output; a fresh judge checks whether each capture shows your claim. A pass names the capture and writes a receipt for this commit; anything else stops at a gate with the judge's reason, which `done-done respond` answers.
45
43
 
46
- Claude Code and Codex are proven live. **OpenCode is not yet proven live:** its free tier rejects done-done's tools-off calls with HTTP 403 (`opencode/deepseek-v4.1-flash-free`), so `init`, `doctor --probe` and a judged run are not proven there, and `opencode/muse-spark-1.3-contributor-free` may be absent from the installed catalog. To try OpenCode, choose a model your plan already covers from `opencode models` with `--agent opencode --model <provider/model>`; done-done never needs a new account or API spend.
44
+ Commit `.done-done.yaml` and the verify skill so later runs and your teammates use the same recipe. Captures go to your judge's provider, on your plan or key.
47
45
 
48
- No result? `done-done doctor` changes nothing and names each missing piece, such as a verify skill `init` could not write, with the exact command that fixes it. Commit `.done-done.yaml` and the verify skill so later runs and your teammates use the same recipe. You choose the agents and models; captures go to your chosen judge's provider, on your own plan or key. [Manual CLI](#manual-cli) walks through recording a journey, checking it and saving a receipt.
46
+ ## Why: not just green tests
49
47
 
50
- ## Why
48
+ | | Green tests, or an agent's word | done-done |
49
+ | --- | --- | --- |
50
+ | What is checked | Code paths, types, lint, builds | The app, CLI or site, driven the way a user drives it |
51
+ | Who looks | You, or nobody | A fresh judge agent you chose, tools off, on your plan |
52
+ | Tied to a commit | No | A receipt names the commit and the sha256 of every capture |
53
+ | Stale or reused proof | Passes | Fails: proof from an older commit, or a file used for another claim, is refused |
54
+ | When it goes wrong | "Done" was wrong, later | The judge's reason, a gate to answer, and no receipt |
51
55
 
52
- <a id="no-proof-no-done"></a>
56
+ ## What it checks
53
57
 
54
- **No proof, no "done".** "Done" is easy to say. Screenshots get reused, recordings go stale, and a green test run is not the app.
58
+ - 🧪 **The real product, not code checks.** A journey that only runs tests, typecheck, lint or a build is refused, and so is proof whose captures all come from such a run ([rule](docs/reference.md#real-product-evidence)).
59
+ - 🔗 **Tied to a commit, fresh, not reused.** Every run records the commit and whether the tree was dirty; older-commit proof fails, and a file used for another claim is reuse ([how](docs/reference.md#where-proof-lives)).
60
+ - 👀 **A judge always answers.** A fresh, tools-off judge on your plan looks at every `check`, `receipt` and `verify`. It cannot be turned off, and a judge that fails to answer stops the command instead of passing ([how](docs/reference.md#judge-relevance)).
61
+ - 🧾 **Honest claims, no model.** `done-done lint <PR URL | file> <evidence folder>` gates a worker's `## Checks` block: four filled lines, and evidence that exists, differs, is newer than the folder and was never used by another PR ([how](docs/reference.md#lint-check)).
62
+ - 🚦 **CI gate, no key.** `done-done gate` passes when a committed receipt covers the head; CI never runs `check`, `run` or `verify` and never gets a model key ([how](docs/reference.md#ci-gate)).
63
+ - 🎨 **Looks right, fits the screen.** `done-done design` has the judge compare a screen with its approved mock; `done-done look <url>` fails a 390 px page that overflows, with no model ([design](docs/reference.md#design-check), [look](docs/reference.md#look-check)).
55
64
 
56
- done-done runs your capture and ties every screenshot, recording and log it saves to the exact commit. When someone says "done", it checks the proof they point to: is it really there, is it a real picture or video, is it from this commit, and is it new? If not, the check fails. If it can't tell, it says so instead of passing.
65
+ Every answer is an exit code: **0** pass, **1** fail, **2** usage or config error, **3** can't tell either way, **4** a run is still going or waits at its gate ([all codes](docs/reference.md#exit-codes)).
57
66
 
58
67
  ## See it work
59
68
 
60
- Four moments from a demo notes app. Every picture is real output from the current build; the demo's judge is a stub that says the screen shows the outcome, so no model runs.
69
+ Four more moments from a demo notes app: a capture for the current commit, a judged pass, a check that refuses an older commit and two bad files, and a config error that stops the run. Real output from the current build; these four use a stub judge that says the screen shows the outcome, so no model runs.
61
70
 
62
71
  <picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/run-dark.svg"><img src="docs/media/run-light.svg" alt="done-done run sync records one screenshot for the current commit." width="400" align="top"></picture>
63
72
  <picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/pass-dark.svg"><img src="docs/media/pass-light.svg" alt="A Checks block cites the new screenshot, the judge passes it, and done-done check passes." width="400" align="top"></picture>
@@ -65,52 +74,21 @@ Four moments from a demo notes app. Every picture is real output from the curren
65
74
  <picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/reasons-dark.svg"><img src="docs/media/reasons-light.svg" alt="done-done check fails: one screenshot is from an older commit, one file is empty, one is a blank frame." width="400" align="top"></picture>
66
75
  <picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/config-dark.svg"><img src="docs/media/config-light.svg" alt="done-done run stops with exit 2 because the config has an unknown key." width="400" align="top"></picture>
67
76
 
68
- done-done is an [AXI](https://axi.md), which means -
69
-
70
- - It's just a CLI any capable agent can run without setup.
71
- - It's built for agents: [TOON](https://github.com/toon-format/spec) output by default (`--json` and `--full` when needed), and every result names the next command in its `help` lines.
72
- - The skill below only handles discovery; agents learn done-done by using it.
77
+ The hero above is a real run of the published CLI on a fresh sample repo, replayed line by line: `run` recorded the capture at commit `ca78363`, the Pi judge on `commandcode/deepseek/deepseek-v4.1-flash` read it and passed it with that reason, `receipt` wrote the receipt, and `init` refused a journey whose only command was `npm test`. [The manual walkthrough](docs/manual-cli.md) does the same by hand.
73
78
 
74
- ## Use It From Your Agent
79
+ ## Use it from your agent
75
80
 
76
- You need Node 22.18 or later and git. Give your agent the done-done skill in the [Agent Skills](https://agentskills.io) format with [`npx skills`](https://github.com/vercel-labs/skills), naming your agent with `-a`:
81
+ Give your agent the done-done skill in the [Agent Skills](https://agentskills.io) format, naming your agent with `-a`:
77
82
 
78
83
  ```sh
79
84
  npx skills add umeranjum17/done-done-axi --skill done-done -a <agent> -y
80
85
  ```
81
86
 
82
- `<agent>` is `claude-code`, `codex`, `opencode`, `pi` or `gemini-cli`. `-a` and `-y` skip the installer's four prompts and keep it from writing skill folders for agents you do not use.
87
+ `<agent>` is `claude-code`, `codex`, `opencode`, `pi` or `gemini-cli`. The skill lands in this project (`.claude/skills/done-done/` for Claude Code, `.agents/skills/done-done/` for the rest) next to a `skills-lock.json`: commit both so your teammates get it, or add `-g` to install it once for all projects. No npm install is needed: the skill runs `done-done` from your PATH, else the npm package through `npx`. Then ask in plain words ("prove the app in this repo works") or use your agent's slash command.
83
88
 
84
- The skill lands in this project (`.claude/skills/done-done/` for Claude Code, `.agents/skills/done-done/` for Codex, opencode, Pi and Gemini CLI) next to a `skills-lock.json`: commit both so your teammates get it too. Or add `-g` to install it once for all your projects (for example `~/.claude/skills/` for Claude Code) and leave the project untouched.
89
+ ## Journeys in plain words
85
90
 
86
- That is the entire setup - no npm install needed.
87
- The skill tells your agent to use `done-done` if it is on PATH and otherwise run the npm package through `npx`, so the CLI comes along on demand:
88
-
89
- ```sh
90
- npx -y done-done-axi --help
91
- ```
92
-
93
- The skill is a short stub and sends the agent to `--help` and the bare command for current instructions, so an installed copy cannot go stale against a newer CLI.
94
-
95
- Then, in agents that expose skills as slash commands (Claude Code, for example), invoke it directly:
96
-
97
- ```
98
- /done-done prove the app in this repo works
99
- ```
100
-
101
- Or just ask in plain words - "prove the app in this repo works", "is this really done?", "gate this PR on proof" - and the agent loads the skill on its own.
102
-
103
- ## Other Ways to Use done-done
104
-
105
- The skill is the recommended path, but it is not the only one.
106
-
107
- ### Manual CLI
108
-
109
- Prefer a terminal to an agent skill? The [manual walkthrough](docs/manual-cli.md) covers installation without sudo, choosing agents, capture recipes, core and upgrade journeys, hooks, `doctor`, checks, receipts and the verify/fix loop. All behavior details live there or in the [reference](docs/reference.md).
110
-
111
- ## Write journeys in plain words
112
-
113
- A journey is the saved script that drives your app and captures what a user sees. You do not have to write it yourself. Describe it, and the agent you chose at `init` writes it:
91
+ A journey is the saved script that drives your app and captures what a user sees. Describe it and your agent writes it:
114
92
 
115
93
  ```sh
116
94
  done-done journey add "greet Umer and check that Hello Umer is printed"
@@ -118,127 +96,50 @@ done-done journey enhance greet-umer-prints-hello "add checks for an empty name
118
96
  done-done journey improve greet-umer-prints-hello "also check that greeting Zoë prints exactly Hello Zoë"
119
97
  ```
120
98
 
121
- - **`add "<description>"`** writes a new journey: a capture script in `journeys/`, its entry in `.done-done.yaml` and a feature in your verify skill. It names the journey from your words and prints that name.
122
- - **`enhance <name> ["<focus>"]`** deepens one journey you already have with more steps, edge cases and checks, and keeps the checks it has. Leave out the focus for a general pass.
123
- - **`improve <name> "<instruction>"`** makes one specific change to one journey and nothing else.
99
+ `add` writes the script, its config entry and its feature in the verify skill; `enhance` deepens one journey; `improve` makes one change to one journey. Each exercise runs once on uncommitted files, which is a trial, not proof: read the script, commit it, then `done-done run <name>`. The saved script needs no agent or model, so CI can replay it; generation runs only on your machine ([worked transcript](docs/manual-cli.md)).
124
100
 
125
- Each command prints the files it changed, then runs the journey once. This is the real output of the three lines above, from a clean install on a small greeting CLI with Claude Code as the agent (run paths cut short with `…`):
126
-
127
- ```
128
- $ done-done journey add "greet Umer and check that Hello Umer is printed"
129
- Added journey greet-umer-prints-hello through claude. Review these repo changes:
130
- .agents/skills/done-done-verify-hello-umer/features/README.md | 1 +
131
- .done-done.yaml | 3 +++
132
- 2 files changed, 4 insertions(+)
133
- journeys/greet-umer-prints-hello.mjs | 29 new lines
134
- .agents/skills/done-done-verify-hello-umer/features/greet-umer-prints-hello.md | 13 new lines
135
- The first run is a dirty-tree trial, not clean proof. Review and commit, then: done-done run greet-umer-prints-hello
136
- ok greet-umer-prints-hello
137
- command: run
138
- result: recorded
139
- dirty: true
140
- files: 1
141
- evidence[1]: …/terminal.txt
142
-
143
- $ done-done journey enhance greet-umer-prints-hello "add checks for an empty name and an unknown command"
144
- Enhanced journey greet-umer-prints-hello through claude. Review these repo changes:
145
- .../features/greet-umer-prints-hello.md | 8 +++++++
146
- .done-done.yaml | 4 +++-
147
- journeys/greet-umer-prints-hello.mjs | 27 +++++++++++++++++++---
148
- 3 files changed, 35 insertions(+), 4 deletions(-)
149
- …
150
- files: 3
151
- evidence[3]: …/terminal-empty-name.txt,…/terminal-unknown-command.txt,…/terminal.txt
152
-
153
- $ done-done journey improve greet-umer-prints-hello "also check that greeting Zoë prints exactly Hello Zoë"
154
- Improved journey greet-umer-prints-hello through claude. Review these repo changes:
155
- .../features/greet-umer-prints-hello.md | 8 ++++++++
156
- .done-done.yaml | 1 +
157
- journeys/greet-umer-prints-hello.mjs | 11 ++++++++++-
158
- 3 files changed, 19 insertions(+), 1 deletion(-)
159
- …
160
- files: 4
161
- evidence[4]: …/terminal-empty-name.txt,…/terminal-unknown-command.txt,…/terminal-zoe.txt,…/terminal.txt
162
- ```
101
+ ## Inside Firstmate, or another agent fleet
163
102
 
164
- `.done-done.yaml` keeps what you asked for next to the journey:
103
+ done-done works the same with or without Firstmate: the skill and CLI behave identically inside or outside a Firstmate home. [Firstmate](https://github.com/kunchenguid/firstmate) hands tasks to agent workers and merges their work, and [`integrations/firstmate/`](integrations/firstmate/) ships the two files those workers need:
165
104
 
166
- ```yaml
167
- journeys:
168
- greet-umer-prints-hello:
169
- command:
170
- # Enhanced focus: "add checks for an empty name and an unknown command"
171
- # Improved instruction: "also check that greeting Zoë prints exactly Hello Zoë"
172
- node journeys/greet-umer-prints-hello.mjs
105
+ ```sh
106
+ npx skills add umeranjum17/done-done-axi --skill done-done -g -a <agent> -y
107
+ npm install -g done-done-axi
108
+ cat "$(npm root -g)/done-done-axi/integrations/firstmate/brief-include.md" >> "$FM_HOME/config/brief-include.md"
109
+ cp "$(npm root -g)/done-done-axi/integrations/firstmate/merge-gate" "$FM_HOME/config/merge-gate"
173
110
  ```
174
111
 
175
- That first run is a trial on uncommitted files, not proof. Read the generated script as you would any code an agent wrote, commit it, then `done-done run greet-umer-prints-hello` records clean proof (`dirty: false`). The saved script needs no agent or model, so CI can replay it. Generation itself runs only on your machine, never in CI, and uses only the agent you configured. `done-done journey --help` shows these examples; the [reference](docs/reference.md#add-a-journey-from-a-description) has every rule.
176
-
177
- ## What it checks
178
-
179
- - 🧪 **The real product, not code checks**: Proof must come from driving the app, CLI or site the way a user does. A config whose journeys only run tests, typecheck, lint or a build, or proof whose captures all come from such runs, is refused by `check`, `receipt` and `gate`, and `doctor` flags it ([rule](docs/reference.md#real-product-evidence)).
180
- - 🎯 **The core journey and the upgrade users take**: Each app names the one flow a user cannot live without, driven end to end on the real target, and an app that ships an installable artifact also names the upgrade over its previous release. `doctor` and the verify skill `init` writes require both ([rule](docs/reference.md#core-journey-and-release-path)).
181
- - 🧭 **The journeys your change needs**: `done-done affected [<base>]` reads the diff since the last receipt (or the default branch), maps each changed file to the journeys whose command runs it and the verify skill features whose file cites it, adds the core journey when a file is in no feature or journey, and exits 1 until a receipt for HEAD has a run of each. It flags the files no feature covers, so the feature map grows with the code ([how](docs/reference.md#affected-journeys)).
182
- - 🔗 **Tied to a commit**: Every run records the commit, and whether there were uncommitted changes.
183
- - 🖼️ **Real media**: Empty, cut-off, blank or tiny screenshots fail. Videos must hold real frames (needs `ffprobe`).
184
- - ⏱️ **Fresh**: Proof from an older commit fails. Commits that only touch docs can be set to keep it fresh.
185
- - ♻️ **Not reused**: Proof made only of files already used for a different claim fails, and so does citing the same file twice. A claim is named by `--claim-id <id>`, which defaults to your branch, so rewording it keeps its proof. Several claims on one branch each need their own `--claim-id`.
186
- - 🧾 **Honest claims**: Reads a pull request's `## Checks` block — from a file, a URL, or stdin — and makes sure every line is filled in.
187
- - 📌 **Verifiable later**: `receipt` records a passing check against the exact commit and the sha256 of every file it cited; `receipt --verify` re-checks it from any checkout of that commit on the same machine (a merge gate or a fresh clone) ([how](docs/reference.md#where-proof-lives)).
188
- - 👀 **Shows the outcome** (always): `check`, `receipt` and `verify` start done-done's own judge, a fresh, tools-off agent on your own plan or key (Claude Code, opencode, pi or Gemini CLI, or your own OpenAI API key), to say whether each capture is the right screen for the claim (and each text capture, such as a log, JSON or CLI output, the right output), and a wrong one fails. There is no way to turn it off: done-done gives no result without it. The checks above never pass on their own, and a check that cites only files the judge cannot read (a video) stops before the judge, with exit 2. You choose the judge route (`done-done init --judge <agent> --judge-model <model>`, or pick it from the list done-done shows in a terminal; `done-done check --judge <route>` overrides it for one call); for a Pi judge with no explicit model, done-done defaults to `openai-codex/gpt-6-luna` unless `agent_config.pi.model` is set. Any agent and model you choose runs, your coding agent's own too. A judge that fails or times out stops the command with its cause (exit 2; a `verify` run stops with exit 2 and its cause in the run record), and no receipt is written.
189
- - 🎨 **Looks right**: `done-done design <screens...> [--mock <screen>=<mock>]` has the same judge look at each captured screen, alone or next to its approved mock, and fails it on stretched or squashed images, clipped or squashed text, overlap, elements off-screen, blank or black strips and broken layout, naming each defect and where it is ([how](docs/reference.md#design-check)).
190
- - 📐 **Fits the screen**: `done-done look <url>` opens a web page in headless Chrome at a 390x844 phone viewport, captures it, and fails the journey that calls it on squashed text, clipped text, elements cut off by an edge or a page wider than the screen, naming each element, with no model ([how](docs/reference.md#look-check)).
191
- - 🧮 **Honest, cheap claim gate**: `done-done lint <PR URL | report file> <evidence folder>` is a no-model gate on a worker's `## Checks` block, on whenever you call it. It prints `PASS` or `FAIL: <reason>` and exits 1 on a fail: the four lines present, proof naming a command or path, a real repro on a fix PR, evidence files that exist, are non-empty, are newer than the evidence folder and differ from each other, at least one never used by another PR (hashes kept in `~/.done-done/lint.hashes`, every run logged to `lint.log` there), under 1,000 added non-generated lines, and a `WARN` when a PR changes `src/` with no `journeys/` file ([how](docs/reference.md#lint-check)). `evidence: not proven: <cause>` passes.
192
- - 🚦 **CI gate, no model**: `done-done gate [<head>]` passes when a receipt committed under `.done-done-receipts/` covers the head and its captures come from real-product runs, with no judge, config, key or evidence files. CI never runs `check`, `run` or `verify` and never gets a model key; proof runs on your desktop ([how](docs/reference.md#ci-gate)).
193
- - 🧩 **Page is clean** (with a `dom` block): each captured screen also needs its page-check record. A page that overflows the viewport, logs a console error, has a failed request or shows squashed, clipped or cut-off UI fails, unless an allow list names it ([how](docs/reference.md#dom-assertions)).
194
-
195
- Every answer is an exit code: **0** pass, **1** fail, **2** usage or config error (including no agent or judge set, a check that fails before its judge, a capture the judge cannot be sent, or a judge that did not answer: never a result), **3** can't tell either way, **4** a `verify`, `maintain` or `rerun` run is still running, or a `verify` run waits at its gate.
196
-
197
- ### Interaction polish needs proof too
198
-
199
- Opt in with `interaction_checklist` in `.done-done.yaml`: detail next/previous plus swipe, instant send/answer state, destructive undo, scroll position on back, long-list search/filter, and useful empty states. Each item needs judged real-product frames or an explicit not-applicable reason; missing proof is flagged, never silently passed. The [shipped checklist and wiring](docs/reference.md#interaction-checklist-opt-in) extend `check`, `receipt` and `verify`; existing approved-design checks stay unchanged without opt-in.
200
-
201
- ## Alone, and with the other tools
202
-
203
- done-done works on its own: the CLI and the skill need nothing else. It is one of three tools that each do one job and connect through small written contracts ([Vision](VISION.md)):
112
+ - **`brief-include.md`**: Firstmate adds `config/brief-include.md` to every worker brief. It tells the worker to set up done-done, run the journeys `done-done affected` lists for its committed change, write a `## Checks` block, pass `check`, record a `receipt`, and see `affected` exit 0.
113
+ - **`merge-gate`** (optional): refuses a PR whose head is not the one asked for, then runs `done-done check --from <PR URL>` and `done-done receipt --verify <head>` against your default branch's rules. Upstream Firstmate does not call a merge gate yet, so run it by hand until it does ([details](docs/reference.md#merge-gate-on-the-desktop)).
204
114
 
205
- - [`sign-off-axi`](https://www.npmjs.com/package/sign-off-axi): the board where agents ask you questions. `done-done signoff` prepares a run's gate (when the judge was unsure) or evidence in sign-off's message format and prints the post command. done-done itself does not publish to a board; sign-off-axi posts the card. With the opt-in (`done-done signoff --post`, or `signoff.post: true` in `.done-done.yaml`), done-done hands the run to sign-off-axi, which posts the message and returns a card that done-done records. `done-done signoff --wait` brings the answer back to the same run. Without sign-off you answer at the terminal with `done-done respond`.
206
- - [`learn-from-mistakes-axi`](https://www.npmjs.com/package/learn-from-mistakes-axi): the retro tool that finds mistakes your agents keep repeating, with evidence. Neither tool needs the other.
207
- - [`first-draft-axi`](https://www.npmjs.com/package/first-draft-axi): turns a prompt into design mocks. Pass a mock to `done-done design <screen> --mock <screen>=<mock>` to judge a captured screen next to it.
115
+ Another fleet works the same way: add the include text to whatever your workers read first, and run `merge-gate` before each merge, with `FM_HOME` set to the folder that holds `data/<task-id>/checks.md` for local-only tasks. Evidence and receipts stay in `~/.done-done/evidence/<app>` after teardown.
208
116
 
209
- ## Use with Firstmate or another agent fleet
117
+ ## The other two tools
210
118
 
211
- done-done works the same with or without Firstmate: nothing below is needed to use it, and the skill and CLI behave identically inside or outside a Firstmate home.
119
+ done-done works on its own: the CLI and the skill need nothing else. It is one of three tools that each do one job and connect through small written contracts ([Vision](VISION.md)): [sign-off-axi](https://www.npmjs.com/package/sign-off-axi) is the board where agents ask you questions (`done-done signoff` prepares a run's card, `--wait` brings the answer back, and the opt-in posts it); [learn-from-mistakes-axi](https://www.npmjs.com/package/learn-from-mistakes-axi) finds the mistakes your agents keep repeating; [first-draft-axi](https://www.npmjs.com/package/first-draft-axi) turns a prompt into design mocks you can judge a capture against.
212
120
 
121
+ ## Known limits
213
122
 
214
- [Firstmate](https://github.com/kunchenguid/firstmate) hands tasks to agent workers and merges their work. done-done ships what those workers need to prove each task, and what the merge needs to check that proof, in [`integrations/firstmate/`](integrations/firstmate/).
123
+ - **Claude Code and Codex are proven live. OpenCode is not yet proven live** on our test machine ([why, and what to try](docs/reference.md#agent-and-judge-support-today)).
124
+ - Codex readers and probes need a CLI with `exec --ignore-user-config` and `debug models --bundled`. Codex is not a judge route; a Pi judge on `openai-codex/gpt-6.1-sol` judges through your existing Codex login.
125
+ - done-done keeps no login of its own: every agent and judge call runs your installed, signed-in CLI on your plan or key.
126
+ - Early preview: behaviour and config keys can change between minor versions.
215
127
 
216
- Install the skill once per machine for your workers' agent (the same `-a` values), install the CLI from npm, and copy two files from it into your Firstmate home:
128
+ ## Manual CLI
217
129
 
218
- ```sh
219
- npx skills add umeranjum17/done-done-axi --skill done-done -g -a <agent> -y
220
- npm install -g done-done-axi
221
- cat "$(npm root -g)/done-done-axi/integrations/firstmate/brief-include.md" >> "$FM_HOME/config/brief-include.md"
222
- cp "$(npm root -g)/done-done-axi/integrations/firstmate/merge-gate" "$FM_HOME/config/merge-gate"
223
- ```
224
-
225
- - **`brief-include.md`**: Firstmate adds `config/brief-include.md` to every worker brief. It tells the worker to set up done-done, run the journeys `done-done affected` lists for its committed change, write a `## Checks` block, pass `check`, record a `receipt`, and see `affected` exit 0. The evidence and receipt stay in `~/.done-done/evidence/<app>` after teardown deletes the worktree.
226
- - **`merge-gate`** (optional): `merge-gate <task-id> <worktree|-> <head> <pr-url|->`. In the worktree, it refuses a PR whose head is not `<head>`, then runs `done-done check --from <PR URL or $FM_HOME/data/<task-id>/checks.md> --trusted-ref <default branch>` (`origin/HEAD`, else `origin/main` or `origin/master`, else the local `main` or `master` in a repo with no remote), then `done-done receipt --verify <head>`. Both paths read done-done's rules from your default branch, never from the task's own branch, so merge `.done-done.yaml` there before you rely on it. The gate does not fetch: it reads the local `origin` refs, so run `git fetch origin` before you run it. It needs `done-done` on PATH (the `npm install` line above). A nonzero exit refuses the merge, and done-done's output says why. A worktree of `-` means the task recorded none, so there is no repo to check: the gate refuses (exit 2) rather than check whatever folder it was called from. Its `check` always starts the judge the user set, which must pass each capture; with none set, the gate refuses (exit 2). Upstream Firstmate does not call a merge gate yet, so this file has no effect until it does; you can run it by hand before a merge.
227
-
228
- Another fleet works the same way: add the include text to whatever your workers read first, and run `merge-gate` before each merge, with `FM_HOME` set to the folder that holds `data/<task-id>/checks.md` for local-only tasks.
130
+ Prefer a terminal to an agent skill? The [manual walkthrough](docs/manual-cli.md) covers installation without sudo, choosing agents, capture recipes, core and upgrade journeys, hooks, `doctor`, checks, receipts and the verify/fix loop. Every behaviour detail is there or in the [full reference](docs/reference.md).
229
131
 
230
132
  ## Learn more
231
133
 
232
134
  - [Vision](VISION.md): who done-done is for, the problem, where it is going, what it is not, and the next milestones.
233
- - [Worked example: a CLI project](docs/example-cli.md): set up, one journey, check and receipt on a command-line tool.
234
- - [Worked example: create and maintain a verify skill](docs/example-create-maintain.md): init writes the skill, a source change makes it drift, maintain fixes it, then a real browser journey is judged.
135
+ - [Worked example: a CLI project](docs/example-cli.md) and [create and maintain a verify skill](docs/example-create-maintain.md).
235
136
  - [Full reference](docs/reference.md): every command and option, the Checks block rules, each file check, and both config files.
236
- - [Config reference](docs/reference.md#config): optional global `judge.account_command` selects a pooled Claude account for judge and agent calls; an exported `CLAUDE_CONFIG_DIR` wins, and a failed pick stops the call.
137
+ - [Config reference](docs/reference.md#config): pooled Claude accounts for judge and agent calls, and the Codex fallback route.
237
138
  - [Report a problem or ask a question](https://github.com/umeranjum17/done-done-axi/issues)
238
139
 
239
140
  ## What's next
240
141
 
241
- So far: `init`, `doctor`, `run` (including `--open` and `--close`), `check`, `receipt`, `affected` (the journeys a diff needs), `lint` (a no-model gate on a Checks block), `gate` (a no-model CI check of a committed receipt), `verify` (by journey or by your coding agent, with a gate you answer with `respond`), `journey add`, `enhance` and `improve` (journeys from plain words), `maintain`, the capture judge (always on), the `design` visual check, the `look` step, the run tools (`status`, `attach`, `logs`, `rerun`, `abort`, `signoff`, `history`) and the opt-in session hooks for Claude Code, Codex, opencode, Gemini CLI and pi. Coming next: the [milestones in the vision](VISION.md#next-milestones).
142
+ Early preview: behaviour and config keys can change between minor versions. Shipped so far: `init`, `doctor`, `run`, `check`, `receipt`, `verify`, `journey add|enhance|improve`, `maintain`, `affected`, `lint`, `gate`, `design`, `look`, and the run tools (`status`, `attach`, `logs`, `rerun`, `abort`, `signoff`, `history`). Next: the [milestones in the vision](VISION.md#next-milestones).
242
143
 
243
144
  ## Develop
244
145
 
@@ -246,20 +147,9 @@ So far: `init`, `doctor`, `run` (including `--open` and `--close`), `check`, `re
246
147
  git clone https://github.com/umeranjum17/done-done-axi
247
148
  cd done-done-axi
248
149
  npm install
249
- npm install -g --prefix ~/.local . # optional: links this checkout as done-done on your PATH
250
- npm test # builds, then runs the end-to-end tests against throwaway git repos
251
- npm run build:skill # regenerate skills/done-done/SKILL.md from src/skill.ts
252
- npm run build:skill -- --check # fail if the committed skill is out of date
253
- ```
254
-
255
- To try an unreleased commit without cloning, run it straight from GitHub (npm 12 needs `--allow-git=all`):
256
-
257
- ```sh
258
- npx -y --allow-git=all github:umeranjum17/done-done-axi --help
150
+ npm test
259
151
  ```
260
152
 
261
- `dist/` is committed, so installing from GitHub needs no build step. `npm test` rebuilds it and fails while the rebuilt `dist/` differs from the commit or has files git does not track, so `git add` the new `dist/` files with your change.
262
-
263
- The first-screen PNG pairs an actual sample-app screenshot with its real configured-judge pass and captured commit. Start a fresh sample with `node docs/media/demo.mjs <fresh-scratch-directory> 0` (the server prints its owned port), open a native run in its `app/` repo, drive the checklist in the browser, save `app.png` at 400×140 DPR 2 into the run's evidence folder, seal it and run `check` with your configured judge. Save the open result as `<scratch>/open.json` and the check result as `<scratch>/check.json`; `/proof` displays that screenshot and verdict. Capture `/proof` at 400×230 DPR 2 with `chrome-devtools-axi`. It refuses to show a pass without a real passing result. Keep scratch outside the repo and stop the server afterwards. The lower terminal illustrations (hero, run, pass, reasons, config) use a stub judge (no model); redraw them with `npx tsc && node docs/media/make.mjs`. The logo (`docs/logo.svg`) and the flow picture are hand-made SVGs.
153
+ `npm test` builds, then runs the end-to-end tests against throwaway git repos. `dist/` is committed, so installing from GitHub needs no build step; `npm test` fails while the rebuilt `dist/` differs from the commit, so `git add` the new files with your change. [Working on done-done](docs/reference.md#working-on-done-done) covers the skill build, the sample app and the screenshots.
264
154
 
265
155
  The landing page ([`docs/index.html`](docs/index.html), served by GitHub Pages from `main` `/docs`) is static: change it in the same PR as any README change it repeats (install steps, what it checks, the other tools).
package/dist/agents.js CHANGED
@@ -397,10 +397,10 @@ export function selectJudge(cfg, found) {
397
397
  : agent !== knownAgentOf(found.agent) ? 'agent' : found.model && model !== found.model ? 'model' : 'session-only';
398
398
  return { route, agent, model, independence, author: { agent: found.agent, model: found.model } };
399
399
  }
400
- // The judge that answers when the set route's call fails (FALLBACK_ROUTES), at that agent's own default model; none for a route with no fallback.
400
+ // One configured second route, else the legacy MiniMax fallback; use its own model, never the primary's.
401
401
  export function selectFallback(cfg, found) {
402
- const route = FALLBACK_ROUTES[cfg.judge.route];
403
- return route ? selectJudge({ ...cfg, judge: { ...cfg.judge, route, model: '' } }, found) : undefined;
402
+ const route = cfg.judge.fallback_route || FALLBACK_ROUTES[cfg.judge.route];
403
+ return route && route !== cfg.judge.route ? selectJudge({ ...cfg, judge: { ...cfg.judge, route, model: '' } }, found) : undefined;
404
404
  }
405
405
  const DRAIN_MS = 500;
406
406
  const OUTPUT_CAP = 64 << 20;
@@ -510,13 +510,16 @@ function start(bin, argv, c, cwd, input, env, idleMs) {
510
510
  child.stdin.end(input);
511
511
  });
512
512
  }
513
+ // A valid picker refusal is availability, not a malformed account configuration.
514
+ export class ClaudeUnavailable extends UsageError {
515
+ }
513
516
  let accountPick;
514
517
  // One pick (including failure) per process, shared by readers, drives and the judge; never change the caller's env.
515
518
  export function claudeAccount(agent, command) {
516
519
  if (agent !== 'claude' || !command || process.env.CLAUDE_CONFIG_DIR !== undefined)
517
520
  return Promise.resolve(undefined);
518
521
  return accountPick ??= Promise.resolve().then(() => {
519
- const fail = (why) => { throw new UsageError(`judge.account_command: ${why}`, 'Fix judge.account_command in the global config, or export CLAUDE_CONFIG_DIR for this call'); };
522
+ const fail = (why, unavailable = false) => { throw new (unavailable ? ClaudeUnavailable : UsageError)(`judge.account_command: ${why}`, 'Fix judge.account_command in the global config, or export CLAUDE_CONFIG_DIR for this call'); };
520
523
  const r = spawnSync(command, { shell: true, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 });
521
524
  if (r.error)
522
525
  fail(r.error.code ?? 'command could not start');
@@ -528,9 +531,9 @@ export function claudeAccount(agent, command) {
528
531
  fail(r.status === 0 ? 'invalid JSON' : `command exited ${r.status}: ${errorLine(r.stderr) || 'no pick returned'}`);
529
532
  }
530
533
  if (r.status !== 0)
531
- fail(`command exited ${r.status}: ${typeof pick?.reason === 'string' ? errorLine(pick.reason) : errorLine(r.stderr) || 'no pick returned'}`);
534
+ fail(`command exited ${r.status}: ${typeof pick?.reason === 'string' ? errorLine(pick.reason) : errorLine(r.stderr) || 'no pick returned'}`, pick?.ok === false || pick === null);
532
535
  if (pick?.ok !== true)
533
- fail(typeof pick?.reason === 'string' ? errorLine(pick.reason) : 'ok must be true');
536
+ fail(typeof pick?.reason === 'string' ? errorLine(pick.reason) : 'ok must be true', pick?.ok === false || pick === null);
534
537
  if (typeof pick.id !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(pick.id))
535
538
  fail('invalid account id');
536
539
  const folder = pick.set?.CLAUDE_CONFIG_DIR;
package/dist/cli.js CHANGED
@@ -47,6 +47,8 @@ const JOURNEYS_TEXT = `journeys (.done-done.yaml): the command runs with DONE_DO
47
47
  spends (journeys.<name>.spends): what a run spends, e.g. "one live generation"; verify parks the journey until the user approves that run (respond --action approve)
48
48
  timeout (journeys.<name>.timeout): how long a verify run may drive the journey, e.g. 90s or 30m (default 30m); past it the journey is killed and the run is inconclusive
49
49
  matrix (.done-done.yaml): with theme, width or form axes the journey runs once per required cell with DONE_DONE_THEME, DONE_DONE_WIDTH, DONE_DONE_FORM and DONE_DONE_CELL (the capture name suffix) set; name each capture with its suffix, so every cell is captured or skipped with a reason`;
50
+ // A claim about how the product looks or what it outputs needs a comparison with the approved result.
51
+ const DESIGN_CLAIM = /\b(design(ed|s)?|redesign(ed)?|mock(up)?s?|prototype|approved)\b/i;
50
52
  const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then these four keys, each at the start of a line:
51
53
  outcome: <what a user can now do>
52
54
  repro: <command that failed before and passes now, or n/a; a fix PR needs one>
@@ -54,6 +56,8 @@ const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then
54
56
  evidence: <the evidence file paths, or "not proven: <cause>">
55
57
  approved_design (.done-done.yaml): a non-empty manifest of {screen, size: <width>x<height>, theme, mock: <repo PNG/JPEG>, capture: <unique filename>};
56
58
  every claim needs every listed live screen, paired with its mock and a passing design verdict; missing or unsure matches fail by screen/size/theme
59
+ approved_requirements (.done-done.yaml): approved assets, color tokens and interactions as {kind: asset|color|interaction, name, capture, mock?}; skip one only with {kind, name, exclusion: <reason>}
60
+ (reference images under design/, mocks/, approved/ or prototype/ must be listed or excluded; a design claim with no approved reference or base_ref baseline fails as not compared to approved result)
57
61
  interaction_checklist (.done-done.yaml, opt-in): map the six ids in skills/done-done/interaction-checklist.json to {captures: [<live PNG/JPEG filename>, ...]}
58
62
  or {not_applicable: <reason>}; detail-navigation, instant-state, destructive-undo, scroll-restoration, list-search, empty-next-step.
59
63
  check/receipt/verify report every item: pass only with judged real-product frames, not_applicable with a reason, otherwise flagged (exit 1).
@@ -113,7 +117,9 @@ const CONFIG_TEXT = `config: ~/.done-done/config.yaml (global; DONE_DONE_HOME mo
113
117
  agent (either file): claude, codex, opencode, pi, gemini or acp:<target>, set by the user (done-done init --agent); done-done never picks one.
114
118
  global only: agent_config.<agent>.{model,effort} (effort: minimal, low, medium, high, xhigh, max), agent_args_override.<agent>
115
119
  (extra flags), agent_path_override.<agent> (binary), acpx_path, agent_timeout (one agent call, default 30m; a streaming agent that prints nothing for 5m is stopped early),
116
- judge.account_command (optional shell command printing a Claude account pick; exported CLAUDE_CONFIG_DIR wins; failure stops the call),
120
+ judge.account_command (optional Claude account pick; exported CLAUDE_CONFIG_DIR wins),
121
+ judge.fallback_route (optional second judge, same values as judge.route; Claude usage/rate/sign-in limits or an empty pool trigger it;
122
+ e.g. answerer:pi with agent_config.pi.model: openai-codex/gpt-6.1-sol; unset keeps Claude failures as errors; no fallback chains),
117
123
  evidence.local_root, judge.{route,model,floor} (the judge and shared confidence floor, set by the user with
118
124
  init --judge; never picked). done-done doctor shows where each agent setting came from.`;
119
125
  // One table drives `--help` and every `<command> --help`: each command's usage lines and the flags it takes.
@@ -909,6 +915,20 @@ async function runCheck(argv, run) {
909
915
  else if (`${live[0].media.w}x${live[0].media.h}` !== e.size)
910
916
  coverageFaults.push(`${label}: live capture is ${live[0].media.w}x${live[0].media.h}, not the approved size`);
911
917
  }
918
+ // Approved assets, color tokens and interactions: each needs its own live capture, or an explicit exclusion with a reason.
919
+ const requirements = cfg.approved_requirements.filter(r => r.capture).map(r => ({ r, live: captures.filter(c => screen(c) && basename(cite(c.name)) === r.capture) }));
920
+ for (const { r, live } of requirements)
921
+ if (live.length !== 1)
922
+ coverageFaults.push(`${r.kind} ${r.name}: ${live.length ? 'ambiguous live captures' : 'no live capture'} (${r.capture})`);
923
+ // Committed approved reference images (design/, mocks/ ...) that no manifest row or exclusion accounts for.
924
+ const accounted = new Set([...cfg.approved_design.map(e => e.mock), ...cfg.approved_requirements.flatMap(r => [r.mock, r.kind === 'reference' ? r.name : undefined])]);
925
+ const unlisted = claim ? git(repo, 'ls-files', '-z', '--', ':(icase)design*/**', ':(icase)mock*/**', ':(icase)approved*/**', ':(icase)prototype*/**').split('\0').filter(f => /\.(png|jpe?g)$/i.test(f) && !accounted.has(f)) : [];
926
+ if (unlisted.length)
927
+ coverageFaults.push(`approved reference material is not in the approved_design manifest: ${unlisted.join(', ')}; list each as an approved_design screen, an approved_requirements mock, or {kind: reference, name: <path>, exclusion: <reason>}`);
928
+ // A design or output claim must be compared to the approved result (a manifest mock, or the base_ref baseline), not only rendered.
929
+ const compared = approved.length > 0 || requirements.some(({ r }) => r.mock) || heads.some(([c]) => c.before);
930
+ if (claim && !compared && DESIGN_CLAIM.test(claim.outcome))
931
+ coverageFaults.push('not compared to approved result: the claim is about design or output but nothing is compared to an approved reference or a same-model baseline; add an approved_design manifest (or an approved_requirements row with a mock) or set base_ref, because render, fit and dark-mode checks alone do not prove it');
912
932
  for (const why of coverageFaults)
913
933
  findings.push({ level: 'fail', why: `approved design: ${why}` });
914
934
  // A head capture of a base_ref run with no base capture of its name is reported, then judged alone.
@@ -993,28 +1013,29 @@ async function runCheck(argv, run) {
993
1013
  if (j.notJudged.length)
994
1014
  judged.not_judged = j.notJudged;
995
1015
  judged.note = 'PNG and JPEG screens and text captures (logs, JSON, CLI output) are judged; video and other files are not';
1016
+ const pairUp = (label, mockFile, live, size) => {
1017
+ const path = resolve(repo, mockFile);
1018
+ let committed;
1019
+ try {
1020
+ committed = execFileSync('git', ['show', `${values['trusted-ref'] ?? now.commit}:${relative(repo, path)}`], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
1021
+ }
1022
+ catch {
1023
+ throw new UsageError(`approved design: ${label}: approved mock is not committed: ${mockFile}`);
1024
+ }
1025
+ if (under(repo, realpathSync(path)) === null)
1026
+ throw new UsageError(`approved design: mock leaves the repo: ${mockFile}`);
1027
+ const mock = loadScreen(path);
1028
+ if (mock.sha256 !== sha256(committed))
1029
+ throw new UsageError(`approved design: approved mock differs from the committed reference: ${mockFile}`);
1030
+ if (size && `${mock.media.w}x${mock.media.h}` !== size)
1031
+ throw new UsageError(`approved design: ${label}: mock has the wrong pixel size`);
1032
+ return { ...live, mock };
1033
+ };
1034
+ const agentRecords = run ? join(run.dir, 'agents') : join(home(), 'agents');
996
1035
  let matches = [];
997
1036
  if (approved.length) {
998
- const pairs = approved.map(({ entry, live }) => {
999
- const path = resolve(repo, entry.mock);
1000
- let committed;
1001
- try {
1002
- committed = execFileSync('git', ['show', `${values['trusted-ref'] ?? now.commit}:${relative(repo, path)}`], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
1003
- }
1004
- catch {
1005
- throw new UsageError(`approved design: ${entry.screen} / ${entry.size} / ${entry.theme}: approved mock is not committed: ${entry.mock}`);
1006
- }
1007
- if (under(repo, realpathSync(path)) === null)
1008
- throw new UsageError(`approved design: mock leaves the repo: ${entry.mock}`);
1009
- const mock = loadScreen(path);
1010
- if (mock.sha256 !== sha256(committed))
1011
- throw new UsageError(`approved design: approved mock differs from the committed reference: ${entry.mock}`);
1012
- return { ...live[0], mock };
1013
- });
1014
- for (const [i, p] of pairs.entries())
1015
- if (`${p.mock.media.w}x${p.mock.media.h}` !== approved[i].entry.size)
1016
- throw new UsageError(`approved design: ${approved[i].entry.screen} / ${approved[i].entry.size} / ${approved[i].entry.theme}: mock has the wrong pixel size`);
1017
- const verdicts = await design(cfg, choice, pairs, run ? join(run.dir, 'agents') : join(home(), 'agents'));
1037
+ const pairs = approved.map(({ entry, live }) => pairUp(`${entry.screen} / ${entry.size} / ${entry.theme}`, entry.mock, live[0], entry.size));
1038
+ const verdicts = await design(cfg, choice, pairs, agentRecords);
1018
1039
  matches = verdicts.map((v, i) => {
1019
1040
  const { screen, size, theme } = approved[i].entry;
1020
1041
  if (v.verdict !== 'pass')
@@ -1028,7 +1049,17 @@ async function runCheck(argv, run) {
1028
1049
  if (judgeBlock)
1029
1050
  judgeBlock.approved_design = matches;
1030
1051
  }
1031
- const touches = await interactions(cfg, choice, captures, run ? join(run.dir, 'agents') : join(home(), 'agents'));
1052
+ const withMock = requirements.filter(({ r }) => r.mock);
1053
+ if (withMock.length) {
1054
+ const pairs = withMock.map(({ r, live }) => pairUp(`${r.kind} ${r.name}`, r.mock, live[0]));
1055
+ const verdicts = await design(cfg, choice, pairs, agentRecords);
1056
+ for (const [i, v] of verdicts.entries()) {
1057
+ const { r } = withMock[i];
1058
+ if (v.verdict !== 'pass')
1059
+ findings.push({ level: 'fail', why: `approved design: ${r.kind} ${r.name}: ${v.verdict === 'unsure' ? 'match not proven: ' + v.reason : v.defects.map(d => `${d.kind}: ${d.where}`).join('; ')}`, file: v.path });
1060
+ }
1061
+ }
1062
+ const touches = await interactions(cfg, choice, captures, agentRecords);
1032
1063
  for (const r of touches) {
1033
1064
  r.captures = r.captures.map(c => ({ ...c, path: receiptPath(repo, cfg, cite(c.path)) }));
1034
1065
  if (r.status === 'flagged')
@@ -1041,6 +1072,9 @@ async function runCheck(argv, run) {
1041
1072
  rep.result.interaction_checklist = touches;
1042
1073
  if (matches.length)
1043
1074
  rep.result.design = matches;
1075
+ const excluded = cfg.approved_requirements.filter(r => r.exclusion).map(({ kind, name, exclusion }) => ({ kind, name, exclusion }));
1076
+ if (excluded.length)
1077
+ rep.result.approved_exclusions = excluded;
1044
1078
  if (rep.code === PASS) {
1045
1079
  mkdirSync(dirname(ledger), { recursive: true });
1046
1080
  appendFileSync(ledger, hashes.filter((h) => !used.get(h)?.has(claimId)).map((h) => `${h} ${claimId}\n`).join(''));
package/dist/config.js CHANGED
@@ -13,10 +13,11 @@ export class UsageError extends Error {
13
13
  }
14
14
  }
15
15
  export const INTERACTIONS = JSON.parse(readFileSync(new URL('../skills/done-done/interaction-checklist.json', import.meta.url), 'utf8'));
16
+ export const REQUIREMENT_KINDS = ['asset', 'color', 'interaction', 'reference'];
16
17
  // The coding agents a user can set as agent.
17
18
  export const AGENTS = ['claude', 'codex', 'opencode', 'pi', 'gemini'];
18
19
  // The agents whose CLI can read an attached image with every tool off, so they judge through the answerer route;
19
- // claude judges through claude-code. An acp:<target> agent cannot be sent an image. Codex image judging is not implemented.
20
+ // claude judges through claude-code. An acp:<target> agent cannot be sent an image. Native Codex image judging is not implemented; use answerer:pi with openai-codex/<model>.
20
21
  export const ANSWERER_JUDGES = ['opencode', 'pi', 'gemini'];
21
22
  // BYOKit key routes: a judge on the user's own API key, read from this environment variable, with no agent CLI.
22
23
  // openai:key needs judge.model; a route with a model here runs it when judge.model is empty (the free MiniMax route).
@@ -69,6 +70,7 @@ const KEYS = {
69
70
  dom: { scope: 'repo', type: 'dom' },
70
71
  look: { scope: 'repo', type: 'look' },
71
72
  approved_design: { scope: 'repo', type: 'approved_design' },
73
+ approved_requirements: { scope: 'repo', type: 'approved_requirements' },
72
74
  interaction_checklist: { scope: 'repo', type: 'interaction_checklist' },
73
75
  base_ref: { scope: 'repo', type: 'string' },
74
76
  'evidence.root': { scope: 'repo', type: 'string' },
@@ -87,6 +89,7 @@ const KEYS = {
87
89
  'judge.floor': { scope: 'global', type: 'number' },
88
90
  'judge.env_file': { scope: 'global', type: 'string' },
89
91
  'judge.account_command': { scope: 'global', type: 'string' },
92
+ 'judge.fallback_route': { scope: 'global', type: 'route' },
90
93
  };
91
94
  const SECTIONS = new Set(['evidence', 'secrets', 'checks', 'judge', 'signoff']);
92
95
  const MAPS = new Set(['auto_fix', 'agent_config', 'agent_args_override', 'agent_path_override']);
@@ -235,6 +238,30 @@ function apply(target, text, file, scope) {
235
238
  target.from[key] = scope;
236
239
  continue;
237
240
  }
241
+ if (key === 'approved_requirements') {
242
+ const fail = () => { throw new UsageError(`${file}: approved_requirements must be a non-empty list of unique {kind: ${REQUIREMENT_KINDS.join('|')}, name, capture: <live PNG/JPEG filename>, mock?: <repo-relative PNG/JPEG>} or {kind, name, exclusion: <reason>}; a reference row names a committed approved image and takes only an exclusion`); };
243
+ const image = (p) => typeof p === 'string' && !isAbsolute(p) && !p.split(/[\\/]/).includes('..') && /\.(png|jpe?g)$/i.test(p);
244
+ if (!Array.isArray(value) || !value.length)
245
+ fail();
246
+ const rows = value;
247
+ const ids = new Set(), names = new Set();
248
+ for (const r of rows) {
249
+ if (!isMap(r) || !REQUIREMENT_KINDS.includes(r.kind) || typeof r.name !== 'string' || !r.name.trim())
250
+ fail();
251
+ const keys = Object.keys(r).filter(k => k !== 'kind' && k !== 'name').sort().join();
252
+ if (typeof r.exclusion === 'string' ? !r.exclusion.trim() || keys !== 'exclusion' : r.kind === 'reference' || !['capture', 'capture,mock'].includes(keys) || !image(r.capture) || /[\\/]/.test(r.capture) || (r.mock !== undefined && !image(r.mock)))
253
+ fail();
254
+ const id = JSON.stringify([r.kind, r.name]);
255
+ if (ids.has(id) || (r.capture && names.has(r.capture)))
256
+ fail();
257
+ ids.add(id);
258
+ if (r.capture)
259
+ names.add(r.capture);
260
+ }
261
+ target.approved_requirements = rows;
262
+ target.from[key] = scope;
263
+ continue;
264
+ }
238
265
  if (key === 'approved_design') {
239
266
  const fail = () => { throw new UsageError(`${file}: approved_design must be a non-empty list of unique {screen, size: <width>x<height> in pixels, theme, mock: <repo-relative PNG/JPEG>, capture: <unique PNG/JPEG filename>}`); };
240
267
  const relativeImage = (p) => typeof p === 'string' && !isAbsolute(p) && !p.split(/[\\/]/).includes('..') && /\.(png|jpe?g)$/i.test(p);
@@ -334,6 +361,7 @@ function defaultConfig(repo) {
334
361
  matrix: null,
335
362
  dom: null,
336
363
  look: null,
364
+ approved_requirements: [],
337
365
  approved_design: [],
338
366
  interaction_checklist: null,
339
367
  base_ref: '',
@@ -341,7 +369,7 @@ function defaultConfig(repo) {
341
369
  secrets: { patterns: {}, allow: [] },
342
370
  checks: { fix_title: '^(fix|bugfix|hotfix)\\b', upstream_catch_up: false, moved_from: false, generated_paths: [] },
343
371
  signoff: { post: false, board: '' },
344
- judge: { route: '', model: '', floor: 0.7, env_file: '', account_command: '' },
372
+ judge: { route: '', fallback_route: '', model: '', floor: 0.7, env_file: '', account_command: '' },
345
373
  trusted: false,
346
374
  from: {},
347
375
  };
@@ -421,6 +449,8 @@ evidence:
421
449
  judge:
422
450
  # Which agent judges: claude-code (claude) or answerer:opencode. Any agent and model you choose, the author's own too.
423
451
  # route: answerer:opencode
452
+ # Optional second judge when Claude has no eligible account, sign-in or usage left; its agent_config model is used.
453
+ # fallback_route: answerer:pi # agent_config.pi.model: openai-codex/gpt-6.1-sol
424
454
  # The judge's model; empty means that agent's agent_config model, or Luna for pi.
425
455
  model: ""
426
456
  # Every judge's pick must reach this probability to count: below it the verdict is unsure (inconclusive), never a pass.
package/dist/judge.js CHANGED
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { accessSync, constants, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { basename, delimiter, isAbsolute, join } from 'node:path';
5
- import { agentRun, askAgent, claudeAccount, notStartable } from './agents.js';
5
+ import { agentRun, askAgent, claudeAccount, ClaudeUnavailable, notStartable } from './agents.js';
6
6
  import { home, KEY_ROUTES, parseDuration, tilde, UsageError } from './config.js';
7
7
  import { downscaleToPng } from './media.js';
8
8
  const MAX_SIDE = 1280; // long edge sent to the model, in pixels
@@ -225,10 +225,11 @@ export async function askJudge(cfg, be, choice, state, questions, images, shared
225
225
  catch (e) {
226
226
  const why = be.lastError() || e.message;
227
227
  if (choice.route === 'claude-code' && /not logged in/i.test(why))
228
- throw new UsageError(`judge: claude is not signed in (${why})`, 'sign in with: claude auth login, then re-run this command');
228
+ throw new ClaudeUnavailable(`judge: claude is not signed in (${why})`, 'sign in with: claude auth login, then re-run this command');
229
229
  const freeTier = choice.agent === 'opencode' && /free tier can only be used from within OpenCode/.test(why);
230
230
  const hint = choice.route === 'claude-code' ? ` (check judge.model ${choice.model} and your plan's usage limit)` : Object.hasOwn(KEY_ROUTES, choice.route) ? ` (check judge.model ${choice.model} and the key in ${KEY_ROUTES[choice.route]})` : freeTier ? ` (${choice.model} is on OpenCode's free tier, which only runs inside OpenCode, and the judge runs with every tool off: set judge.model in ~/.done-done/config.yaml to a model that is not on the free tier)` : '';
231
- throw new UsageError(`judge: the judge did not answer: ${why}${hint}`, again);
231
+ const Failure = choice.route === 'claude-code' && /rate.?limit|usage.?limit|not signed in|(?:every|all|no eligible).*account.*(?:floor|limit|available)/i.test(why) ? ClaudeUnavailable : UsageError;
232
+ throw new Failure(`judge: the judge did not answer: ${why}${hint}`, again);
232
233
  }
233
234
  const bad = malformed(answers);
234
235
  if (bad.length)
@@ -274,11 +275,12 @@ export async function judge(cfg, choose, outcome, captures, records, fallback) {
274
275
  return await judgeOn(cfg, choose, outcome, captures, records);
275
276
  }
276
277
  catch (e) {
277
- const next = e instanceof UsageError && fallback?.();
278
+ const next = e instanceof UsageError && (choose().route !== 'claude-code' || e instanceof ClaudeUnavailable) && fallback?.();
278
279
  if (!next)
279
280
  throw e;
280
281
  try {
281
- return await judgeOn(cfg, () => next, outcome, captures, records);
282
+ const result = await judgeOn(cfg, () => next, outcome, captures, records);
283
+ return { ...result, detail: `${next.route} answered after ${choose().route} unavailable${result.detail ? `; ${result.detail}` : ''}` };
282
284
  }
283
285
  catch (again) {
284
286
  if (!(again instanceof UsageError))
package/docs/index.html CHANGED
@@ -159,7 +159,7 @@ done-done verify --outcome "&lt;what works now&gt;"</code></pre>
159
159
  <section id="firstmate">
160
160
  <h2>Inside Firstmate, or outside</h2>
161
161
  <p class="lead">done-done behaves the same with or without <a href="https://github.com/kunchenguid/firstmate">Firstmate</a>. Nothing here is needed to use it.</p>
162
- <p class="lead">Inside a Firstmate home, <code>integrations/firstmate/</code> ships a brief include that tells each worker to run a journey, write a <code>## Checks</code> block, pass <code>check</code> and record a <code>receipt</code>, and an optional merge gate that refuses a merge without it. A lone agent in a terminal skips all of that. Upstream Firstmate does not call the merge gate yet; run it by hand if you want it to enforce the check. <a href="https://github.com/umeranjum17/done-done-axi#use-with-firstmate-or-another-agent-fleet">Integration instructions</a>.</p>
162
+ <p class="lead">Inside a Firstmate home, <code>integrations/firstmate/</code> ships a brief include that tells each worker to run a journey, write a <code>## Checks</code> block, pass <code>check</code> and record a <code>receipt</code>, and an optional merge gate that refuses a merge without it. A lone agent in a terminal skips all of that. Upstream Firstmate does not call the merge gate yet; run it by hand if you want it to enforce the check. <a href="https://github.com/umeranjum17/done-done-axi#inside-firstmate-or-another-agent-fleet">Integration instructions</a>.</p>
163
163
  </section>
164
164
 
165
165
  </main>
@@ -73,4 +73,70 @@ With `auto_fix: { verify: <n> }` (0, the default, reports only), a failed verify
73
73
 
74
74
  While a `verify` or `maintain` run goes, `done-done status` shows its steps and next move, `done-done attach` streams its events, and `done-done logs --step <name>` shows one step's log. `done-done rerun` runs a failed verify run again from the failed step, `done-done abort` stops a live run, `done-done signoff` writes a verify run's sign-off message, and `done-done history` lists past runs.
75
75
 
76
+ **4. Write a journey from plain words**
77
+
78
+ A journey is the saved script that drives your app and captures what a user sees. You do not have to write it yourself; describe it and the agent you chose at `init` writes it:
79
+
80
+ ```sh
81
+ done-done journey add "greet Umer and check that Hello Umer is printed"
82
+ done-done journey enhance greet-umer-prints-hello "add checks for an empty name and an unknown command"
83
+ done-done journey improve greet-umer-prints-hello "also check that greeting Zoë prints exactly Hello Zoë"
84
+ ```
85
+
86
+ - **`add "<description>"`** writes a new journey: a capture script in `journeys/`, its entry in `.done-done.yaml` and a feature in your verify skill. It names the journey from your words and prints that name.
87
+ - **`enhance <name> ["<focus>"]`** deepens one journey you already have with more steps, edge cases and checks, and keeps the checks it has. Leave out the focus for a general pass.
88
+ - **`improve <name> "<instruction>"`** makes one specific change to one journey and nothing else.
89
+
90
+ Each command prints the files it changed, then runs the journey once. This is the real output of the three lines above, from a clean install on a small greeting CLI with Claude Code as the agent (run paths cut short with `…`):
91
+
92
+ ```
93
+ $ done-done journey add "greet Umer and check that Hello Umer is printed"
94
+ Added journey greet-umer-prints-hello through claude. Review these repo changes:
95
+ .agents/skills/done-done-verify-hello-umer/features/README.md | 1 +
96
+ .done-done.yaml | 3 +++
97
+ 2 files changed, 4 insertions(+)
98
+ journeys/greet-umer-prints-hello.mjs | 29 new lines
99
+ .agents/skills/done-done-verify-hello-umer/features/greet-umer-prints-hello.md | 13 new lines
100
+ The first run is a dirty-tree trial, not clean proof. Review and commit, then: done-done run greet-umer-prints-hello
101
+ ok greet-umer-prints-hello
102
+ command: run
103
+ result: recorded
104
+ dirty: true
105
+ files: 1
106
+ evidence[1]: …/terminal.txt
107
+
108
+ $ done-done journey enhance greet-umer-prints-hello "add checks for an empty name and an unknown command"
109
+ Enhanced journey greet-umer-prints-hello through claude. Review these repo changes:
110
+ .../features/greet-umer-prints-hello.md | 8 +++++++
111
+ .done-done.yaml | 4 +++-
112
+ journeys/greet-umer-prints-hello.mjs | 27 +++++++++++++++++++---
113
+ 3 files changed, 35 insertions(+), 4 deletions(-)
114
+ …
115
+ files: 3
116
+ evidence[3]: …/terminal-empty-name.txt,…/terminal-unknown-command.txt,…/terminal.txt
117
+
118
+ $ done-done journey improve greet-umer-prints-hello "also check that greeting Zoë prints exactly Hello Zoë"
119
+ Improved journey greet-umer-prints-hello through claude. Review these repo changes:
120
+ .../features/greet-umer-prints-hello.md | 8 ++++++++
121
+ .done-done.yaml | 1 +
122
+ journeys/greet-umer-prints-hello.mjs | 11 ++++++++++-
123
+ 3 files changed, 19 insertions(+), 1 deletion(-)
124
+ …
125
+ files: 4
126
+ evidence[4]: …/terminal-empty-name.txt,…/terminal-unknown-command.txt,…/terminal-zoe.txt,…/terminal.txt
127
+ ```
128
+
129
+ `.done-done.yaml` keeps what you asked for next to the journey:
130
+
131
+ ```yaml
132
+ journeys:
133
+ greet-umer-prints-hello:
134
+ command:
135
+ # Enhanced focus: "add checks for an empty name and an unknown command"
136
+ # Improved instruction: "also check that greeting Zoë prints exactly Hello Zoë"
137
+ node journeys/greet-umer-prints-hello.mjs
138
+ ```
139
+
140
+ That first run is a trial on uncommitted files, not proof. Read the generated script as you would any code an agent wrote, commit it, then `done-done run greet-umer-prints-hello` records clean proof (`dirty: false`). The saved script needs no agent or model, so CI can replay it. Generation itself runs only on your machine, never in CI, and uses only the agent you configured. `done-done journey --help` shows these examples; the [reference](reference.md#add-a-journey-from-a-description) has every rule.
141
+
76
142
  Run `done-done` with no arguments to see the current config and the next step.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ $ done-done init --journey tests -- 'npm test'
2
+ command: init
3
+ error: "init --journey tests: npm test is code-only: a test, typecheck, lint or build run proves the code checks pass, not that the product works, so check and gate never count its run as proof; nothing was written"
4
+ exit: 2
@@ -0,0 +1,41 @@
1
+ $ done-done run greet
2
+ command: run
3
+ result: recorded
4
+ run_id: 20261025T033042Z-ca783630-43267e
5
+ commit: ca783630026370864950bce53bc5efab802d29d6
6
+ dirty: false
7
+ files: 1
8
+ record: ~/.done-done/evidence/sample/.done-done/runs/20261025T033042Z-ca783630-43267e.json
9
+ evidence[1]: 20261025T033042Z-ca783630-43267e.terminal.txt
10
+
11
+ $ done-done check --outcome "running greet.mjs with Umer prints Hello Umer" "$evidence"
12
+ command: check
13
+ result: pass
14
+ claim: running greet.mjs with Umer prints Hello Umer
15
+ files: 1
16
+ evidence[1]: 20261025T033042Z-ca783630-43267e.terminal.txt
17
+ judge:
18
+ route: "answerer:pi"
19
+ model: commandcode/deepseek/deepseek-v4.1-flash
20
+ independence: agent
21
+ author: claude (model claude-haiku-5-5)
22
+ judged[1]{path,verdict,reason,seen,cached}:
23
+ 20261025T033042Z-ca783630-43267e.terminal.txt,pass,The output shows running greet.mjs with Umer and printing Hello Umer.,"I read the single capture entry. The command line \"$ node greet.mjs Umer\" runs greet.mjs with Umer, and the following output line \"Hello Umer\" matches the outcome exactly, with no error or contradiction.",false
24
+ note: "PNG and JPEG screens and text captures (logs, JSON, CLI output) are judged; video and other files are not"
25
+
26
+ $ done-done receipt --outcome "running greet.mjs with Umer prints Hello Umer" "$evidence"
27
+ command: receipt
28
+ result: pass
29
+ head: ca783630026370864950bce53bc5efab802d29d6
30
+ tree: 3dff6448314deb0fe0325786bc9a7c2369366be6
31
+ claim: running greet.mjs with Umer prints Hello Umer
32
+ files: 1
33
+ receipt: ~/.done-done/evidence/sample/.done-done/receipts/ca783630026370864950bce53bc5efab802d29d6.json
34
+ judge:
35
+ route: "answerer:pi"
36
+ model: commandcode/deepseek/deepseek-v4.1-flash
37
+ independence: agent
38
+ author: claude (model claude-haiku-5-5)
39
+ judged[1]{path,verdict,reason,seen,cached}:
40
+ 20261025T033042Z-ca783630-43267e.terminal.txt,pass,The output shows running greet.mjs with Umer and printing Hello Umer.,"I read the single capture entry. The command line \"$ node greet.mjs Umer\" runs greet.mjs with Umer, and the following output line \"Hello Umer\" matches the outcome exactly, with no error or contradiction.",true
41
+ note: "PNG and JPEG screens and text captures (logs, JSON, CLI output) are judged; video and other files are not"
Binary file
@@ -0,0 +1,84 @@
1
+ // Build the README hero pages from the captured CLI transcripts in this folder.
2
+ // Every value shown is asserted to exist in a transcript; only field order and long paths change.
3
+ // Usage: node docs/media/hero.mjs, then screenshot .hero/hero-desktop.html (828x596) and
4
+ // .hero/hero-phone.html (418x792) at device scale factor 2, in light and dark:
5
+ // hero-light.png, hero-dark.png (1600 px wide source shown at width 100%), hero-phone-*.png.
6
+ // The animated hero.gif replays the same lines one by one (1200 px wide, 16 frames, ~9 s),
7
+ // ending on the judge's pass and the receipt; it is captured from the same page with the
8
+ // lines revealed a step at a time, one row's worth of pixels cropped off each earlier frame.
9
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const dir = dirname(fileURLToPath(import.meta.url));
14
+ const pass = readFileSync(join(dir, 'hero-run.txt'), 'utf8');
15
+ const refuse = readFileSync(join(dir, 'hero-refuse.txt'), 'utf8');
16
+ const need = (hay, s) => {
17
+ if (!hay.includes(s)) throw new Error(`not in the captured transcript: ${s}`);
18
+ return s;
19
+ };
20
+
21
+ const commit = need(pass, 'ca783630026370864950bce53bc5efab802d29d6');
22
+ const reason = need(pass, 'The output shows running greet.mjs with Umer and printing Hello Umer.');
23
+ const model = need(pass, 'commandcode/deepseek/deepseek-v4.1-flash');
24
+ const err = need(refuse, 'init --journey tests: npm test is code-only: a test, typecheck, lint or build run proves the code checks pass, not that the product works, so check and gate never count its run as proof; nothing was written');
25
+
26
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
27
+ const L = (cls, s) => `<div class="${cls}">${esc(s)}</div>`;
28
+
29
+ const body = ({ phone }) => [
30
+ L('cmd', '$ done-done run greet'),
31
+ L('out', 'result: recorded'),
32
+ L('out', `commit: ${commit}`),
33
+ L('out', 'dirty: false'),
34
+ L('out', 'files: 1'),
35
+ '',
36
+ L('cmd', `$ done-done check --outcome "greeting Umer prints Hello Umer" ${phone ? '…' : '…terminal.txt'}`),
37
+ L('ok', 'result: pass'),
38
+ L('out', 'judge: answerer:pi'),
39
+ L('out', `model: ${model}`),
40
+ L('ok', `reason: "${reason}"`),
41
+ '',
42
+ L('cmd', '$ done-done receipt --outcome "greeting Umer prints Hello Umer"'),
43
+ L('ok', 'result: pass'),
44
+ L('out', `receipt: receipts/${commit}.json`),
45
+ '<div class="rule"></div>',
46
+ L('cmd', "$ done-done init --journey tests -- 'npm test'"),
47
+ L('bad', 'result: fail'),
48
+ L('bad', `error: ${err}`),
49
+ ].join('\n');
50
+
51
+ const page = ({ width, font, phone, title }) => `<!doctype html><meta charset="utf-8"><title>${title}</title>
52
+ <style>
53
+ :root { color-scheme: light dark; }
54
+ html, body { margin: 0; background: #f6f8fa; }
55
+ body { font: ${font}px/1.62 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
56
+ .card { width: ${width}px; box-sizing: border-box; margin: 14px; padding: 16px 18px; border-radius: 10px;
57
+ background: #fff; border: 1px solid #d8dee4; color: #1f2328; }
58
+ .meta { font: 600 12px/1.4 ui-sans-serif, system-ui, sans-serif; letter-spacing: .01em; color: #57606a; margin-bottom: 10px; }
59
+ .cmd { color: #6639ba; font-weight: 600; margin-top: 10px; white-space: pre-wrap; word-break: break-word; }
60
+ .out { color: #1f2328; white-space: pre-wrap; word-break: break-word; }
61
+ .ok { color: #1a7f37; white-space: pre-wrap; word-break: break-word; }
62
+ .bad { color: #b42318; white-space: pre-wrap; word-break: break-word; }
63
+ .rule { border-top: 1px dashed #d8dee4; margin: 14px 0 2px; }
64
+ @media (prefers-color-scheme: dark) {
65
+ html, body { background: #0d1117; }
66
+ .card { background: #161b22; border-color: #30363d; color: #e6edf3; }
67
+ .meta { color: #8b949e; }
68
+ .cmd { color: #d2a8ff; }
69
+ .out { color: #e6edf3; }
70
+ .ok { color: #3fb950; }
71
+ .bad { color: #ff7b72; }
72
+ .rule { border-color: #30363d; }
73
+ }
74
+ </style>
75
+ <div class="card">
76
+ <div class="meta">done-done 0.6.12 · real run on a small sample app</div>
77
+ ${body({ phone })}
78
+ </div>`;
79
+
80
+ const out = join(dir, '.hero');
81
+ mkdirSync(out, { recursive: true });
82
+ writeFileSync(join(out, 'hero-desktop.html'), page({ width: 800, font: 15, phone: false, title: 'done-done hero, desktop' }));
83
+ writeFileSync(join(out, 'hero-phone.html'), page({ width: 390, font: 14, phone: true, title: 'done-done hero, phone' }));
84
+ console.log(`wrote ${out}/hero-desktop.html and ${out}/hero-phone.html`);
package/docs/reference.md CHANGED
@@ -98,7 +98,17 @@ done-done doctor --probe
98
98
 
99
99
  done-done starts the coding agent you choose, and never picks one. `agent:` names exactly one (`claude`, `codex`, `opencode`, `pi`, `gemini` or `acp:<target>`); there is no auto-detection and no fallback list, so an agent that is not installed or cannot start stops the command with the reason. While no agent is set, a command that starts it asks in a terminal (a numbered list of agents, then of models) and saves the answer to the global config; anywhere else (an agent calling done-done) it exits 2 with `no agent is set: ask the user which agent to use (claude, codex, opencode, pi, gemini, acp:<target>); never pick one for them` and the command that saves the answer: `done-done init --agent <agent> --model <model>`. `doctor` reports an unset agent as missing with that same fix. The model is `agent_config.<agent>.model` (global config only), else the agent's cheap default (`claude-haiku-5-5` for claude, `opencode-go/muse-spark-1.3-contributor` for opencode, `flash-lite` for gemini; claude and opencode also default to `effort: low`). That table is the only place done-done names a default model, and `init` writes it into the global config. `agent_config.<agent>.effort` (`minimal`, `low`, `medium`, `high`, `xhigh` or `max`, or `''` for the CLI's own) goes to the CLI's own flag as written: `--effort` (claude), `-c model_reasoning_effort="<level>"` (codex), `--variant` (opencode) and `--thinking` (pi); gemini and `acp:<target>` have none, so an effort for them is a config error. `agent_args_override.<agent>` adds extra CLI flags after done-done's own (do not repeat a flag done-done passes), `agent_path_override.<agent>` starts that binary instead of the one on PATH, `acpx_path` names the acpx bridge, and `agent_timeout` (default `30m`) bounds each agent call: a verify drive, a maintain reader, the judge and the doctor probe. A streaming agent (pi, opencode, codex, `acp:<target>`) that prints nothing, on stdout or stderr, for 5 minutes is stopped early (30 seconds for the doctor probe); the no-answer reason then quotes its stderr and the call record path, so a hung agent costs minutes, not the whole timeout. A rate limit the CLI prints (HTTP 429, a usage limit) ends any agent call at that line, with the limit and its reset time as the reason (for pi's JSON events, the provider's own message, not the event); the one exception is a claude judge call with images, which stops only at claude's own rejected rate-limit event, so its retries run. claude and gemini print one answer at the end, so only `agent_timeout` bounds them. These defaults are the cost guard for those agents: done-done never defaults to a flagship model for them. codex, pi and `acp:<target>` have no public cheap alias, so with no `agent_config.<agent>.model` (for `acp:<target>`, `agent_config.acp:<target>.model`) they run their own configured model, which can be a flagship; that setting caps their cost. `agent: acp:<target>` (for example `acp:gemini`) starts any Agent Client Protocol agent through [acpx](https://github.com/openclaw/acpx), which you install yourself (`npm install -g acpx`). doctor reports an `acp:` agent missing, with that install step, when acpx is not on PATH or does not start.
100
100
 
101
- Each call is a fresh, single-shot session that must answer one JSON object matching a schema. The prompt goes on stdin (claude, codex, pi, gemini, acp) or into a 0600 prompt file (opencode), never in argv. done-done can start claude (`claude -p --output-format json --json-schema <schema> --model <model> --no-session-persistence`, all tools off with `--tools ''`; tool-less calls also pass `--setting-sources ''` and run from a scratch folder, so no project settings or hooks load; read-only calls allow only `--tools Read,Grep,Glob` and pass `--setting-sources ''` and `--strict-mcp-config`, so no user settings or MCP servers load), opencode (`opencode run "Follow the attached instructions." --format json -m <provider/model> -f <prompt file>`, with the schema in the 0600 prompt file and every tool denied through `OPENCODE_PERMISSION` except on an edit step ([Drive by agent](#drive-by-agent), Scope)), pi (`pi -p --mode json --no-session --no-tools`, tools off for every read or probe call, since pi has no read-only mode that refuses outside reads, and `--model <model>` only when one is set) and gemini (`gemini -p "Follow the instructions on stdin." -o json -m <model> --approval-mode plan|auto_edit`, where `plan` is read-only for the probe and read steps and `auto_edit` is used for edit steps; `--skip-trust` is added only to the tool-less probe in its scratch folder, so repo calls keep Gemini's workspace trust gate) and codex (`codex exec --json --ephemeral [-m <model>]`, the answer being the last agent message of its JSONL events; an edit step runs in codex's `--sandbox workspace-write` with the run's evidence and scratch folders added by `--add-dir` and network on, so the drive can launch the app and talk to it, while it keeps your own codex config). Codex readers and probes run a separate `--no-daemon exec --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only` call, with project instructions disabled and your normal authentication untouched. Feature flags alone do not remove all tools: model metadata can force code-mode, collaboration and patch tools back on. done-done reads the CLI's public bundled catalog (`codex debug models --bundled`), writes a scratch-only copy with those capabilities removed, disables the remaining tool features and web search, and supplies it through `model_catalog_json`. A missing catalog, unsupported CLI or model not in that catalog fails with its cause; no unsafe fallback or copied login is used. `doctor --probe` now requires a real Codex answer, and `init` and maintain use the same clean reader. Codex image judging is not implemented, so it remains unavailable as a judge route. `acp:<target>` runs `acpx --cwd <dir> --format json --json-strict --deny-all --non-interactive-permissions deny [--model <model>] <target> exec --file -`, with the prompt and schema on stdin; every tool is denied. pi has no permission gate for edit steps, so doctor adds a `tool_gate` warning row whenever pi is the agent: every pi tool runs without approval. codex's sandbox covers its shell too, so doctor adds a `tool_gate` warning row whenever codex is the agent: a drive's recipe tool can write only where the drive may (see [Drive by agent](#drive-by-agent), Scope).
101
+ Each call is a fresh, single-shot session that must answer one JSON object matching a schema. The prompt goes on stdin (claude, codex, pi, gemini, acp) or into a 0600 prompt file (opencode), never in argv. done-done can start claude (`claude -p --output-format json --json-schema <schema> --model <model> --no-session-persistence`, all tools off with `--tools ''`; tool-less calls also pass `--setting-sources ''` and run from a scratch folder, so no project settings or hooks load; read-only calls allow only `--tools Read,Grep,Glob` and pass `--setting-sources ''` and `--strict-mcp-config`, so no user settings or MCP servers load), opencode (`opencode run "Follow the attached instructions." --format json -m <provider/model> -f <prompt file>`, with the schema in the 0600 prompt file and every tool denied through `OPENCODE_PERMISSION` except on an edit step ([Drive by agent](#drive-by-agent), Scope)), pi (`pi -p --mode json --no-session --no-tools`, tools off for every read or probe call, since pi has no read-only mode that refuses outside reads, and `--model <model>` only when one is set) and gemini (`gemini -p "Follow the instructions on stdin." -o json -m <model> --approval-mode plan|auto_edit`, where `plan` is read-only for the probe and read steps and `auto_edit` is used for edit steps; `--skip-trust` is added only to the tool-less probe in its scratch folder, so repo calls keep Gemini's workspace trust gate) and codex (`codex exec --json --ephemeral [-m <model>]`, the answer being the last agent message of its JSONL events; an edit step runs in codex's `--sandbox workspace-write` with the run's evidence and scratch folders added by `--add-dir` and network on, so the drive can launch the app and talk to it, while it keeps your own codex config). Codex readers and probes run a separate `--no-daemon exec --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only` call, with project instructions disabled and your normal authentication untouched. Feature flags alone do not remove all tools: model metadata can force code-mode, collaboration and patch tools back on. done-done reads the CLI's public bundled catalog (`codex debug models --bundled`), writes a scratch-only copy with those capabilities removed, disables the remaining tool features and web search, and supplies it through `model_catalog_json`. A missing catalog, unsupported CLI or model not in that catalog fails with its cause; no unsafe fallback or copied login is used. `doctor --probe` now requires a real Codex answer, and `init` and maintain use the same clean reader. Native Codex image judging is not implemented; Codex image judging is available through `answerer:pi` with `openai-codex/gpt-6.1-sol` on your existing Codex login. `acp:<target>` runs `acpx --cwd <dir> --format json --json-strict --deny-all --non-interactive-permissions deny [--model <model>] <target> exec --file -`, with the prompt and schema on stdin; every tool is denied. pi has no permission gate for edit steps, so doctor adds a `tool_gate` warning row whenever pi is the agent: every pi tool runs without approval. codex's sandbox covers its shell too, so doctor adds a `tool_gate` warning row whenever codex is the agent: a drive's recipe tool can write only where the drive may (see [Drive by agent](#drive-by-agent), Scope).
102
+
103
+ ### Agent and judge support today
104
+
105
+ Claude Code and Codex are proven live. **OpenCode is not yet proven live:** its free tier rejects done-done's tools-off calls with HTTP 403 (`opencode/deepseek-v4.1-flash-free`), so `init`, `doctor --probe` and a judged run are not proven there, and `opencode/muse-spark-1.3-contributor-free` may be absent from the installed catalog. To try OpenCode, choose a model your plan already covers from `opencode models` with `--agent opencode --model <provider/model>`; done-done never needs a new account or API spend.
106
+
107
+ For a signed-in Claude Code setup, pass `--agent claude --model claude-haiku-5-5 --judge claude --judge-model claude-haiku-5-5` to `init`. For Codex, use `--agent codex --model gpt-6.1-sol` with that same judge. Codex readers and probes require a CLI with `exec --ignore-user-config` and `debug models --bundled`: they keep your login, but disable MCP, project instructions and model-provided tools; edit and drive steps keep your normal Codex config. Codex is not a judge route; a Pi judge on `openai-codex/gpt-6.1-sol` judges through your existing Codex login.
108
+
109
+ `done-done --version` prints the bare version first; help headers show it too, and suffixed versions are labelled `pre-release`.
110
+
111
+ Optional setup check: `done-done doctor --probe` requires a real answer from your chosen agent; it checks setup, not whether your app works.
102
112
 
103
113
  ### Add a journey from a description
104
114
 
@@ -388,7 +398,7 @@ done-done affected origin/main
388
398
 
389
399
  `affected [<base>]` says which journeys a change needs and whether HEAD's proof covers them. It is model-free and reads git, `.done-done.yaml` and the verify skill only.
390
400
 
391
- - **The change.** The files that differ from `<base>`, else from the newest receipt whose head is before HEAD (the commits since the last proof), else from the merge base with the default branch (`origin/HEAD`, `origin/main`, `origin/master`, then the local `main` or `master`, the order the [Firstmate merge gate](../README.md#use-with-firstmate-or-another-agent-fleet) uses), plus uncommitted changes to tracked files. Untracked files are not part of the change until added. A file that matches `evidence.ignore_paths`, or a committed receipt under `.done-done-receipts/` ([CI gate](#ci-gate)), needs nothing.
401
+ - **The change.** The files that differ from `<base>`, else from the newest receipt whose head is before HEAD (the commits since the last proof), else from the merge base with the default branch (`origin/HEAD`, `origin/main`, `origin/master`, then the local `main` or `master`, the order the [Firstmate merge gate](../README.md#inside-firstmate-or-another-agent-fleet) uses), plus uncommitted changes to tracked files. Untracked files are not part of the change until added. A file that matches `evidence.ignore_paths`, or a committed receipt under `.done-done-receipts/` ([CI gate](#ci-gate)), needs nothing.
392
402
  - **The mapping.** A journey is needed when its command runs the changed file (`node journeys/e2e.mjs` runs `journeys/e2e.mjs`). A feature of the verify skill (`features/<name>.md`) is needed when its file cites the changed file: a repo path, a markdown link (read from the feature's folder), a folder written with a slash (`src/` covers every file under it) or a bare file name, which matches each tracked file of that name as `maintain` reads one; or when the feature file itself changed. The skill's `SKILL.md` and `features/README.md` are what every feature follows, so a change to them needs every feature. A needed feature brings each journey it names (`run <journey>` or `verify <journey>` in its text, or a file the journey's command runs that it cites too); a feature that names no journey is driven by your agent: `done-done verify --feature <name>`. A code-only journey ([Real-product evidence](#real-product-evidence)) is never needed.
393
403
  - **Uncertain means more, never less.** A changed file that no feature or journey covers, or no base to diff against, makes the mapping uncertain: the core journey ([Core journey and release path](#core-journey-and-release-path)) is needed too, or the feature the feature map marks `(core journey)`, or every journey when none is named. Each such file is listed under `unmapped` and flagged in `help`: add it to the feature file that drives it (`done-done maintain` reports recent files no feature covers), or record a journey for it.
394
404
  - **The proof.** `fresh` (exit 0) when the receipt for HEAD verifies ([Record a receipt](#record-a-receipt)), its `runs` include a run of every needed journey (by name, or by the journey's command) and every needed feature (a `verify --feature` drive, which the receipt records as the run's `feature`), and no tracked file has an uncommitted change. Otherwise exit 1: `stale` when the newest receipt is for an earlier commit (it names that commit and the journeys the new commits need), when the receipt for HEAD misses a needed journey or feature, or when there are uncommitted changes; `missing` when there is no receipt at all. `help` gives the commands that capture the missing proof (`done-done run <journey>`, `done-done verify --feature <name>`), then the receipt step.
@@ -463,7 +473,20 @@ done-done check --from pr-body.md # always judged
463
473
  - `gemini`: plan mode plus a deny-all `--policy` file, which replaces your own policy folder and removes every tool, MCP ones too; the capture is an `@<file>` in the prompt, which Gemini CLI's own `@`-command code reads (outside the tool policy, per its source; not yet proven live, since Gemini CLI was not signed in on the test machine), only inside the scratch folder.
464
474
  - An `acp:<target>` agent cannot judge, since done-done cannot send it an image; doctor's `config` row says so when `judge.route` names one.
465
475
  - `openai:key`: a key-only route with no agent CLI. BYOKit's OpenAI backend sends the capture to the OpenAI Responses API on your own API key, which done-done reads from `OPENAI_API_KEY` and hands only to the kit (never printed or stored). Set `judge.model` to a model your key can use that reads images; there is no default. API billing applies.
466
- - `minimax:key`: a key-only route on your MiniMax key, with no agent CLI. done-done reads `MINIMAX_API_KEY` and hands it only to the kit's Anthropic backend, which done-done points at MiniMax's Anthropic-compatible endpoint (`https://api.minimax.io/anthropic/v1`, the only address it will send the key to). `judge.model` is optional: empty runs `MiniMax-M3.1-Flash-Preview`, which reads images. This route is the only one with a fallback: when its call fails (a refused or missing key, a rate limit, no answer), `check`, `receipt` and `verify` judge the whole check again on `claude-code` at claude's cheap model (`agent_config.claude.model`, else the default), and the result's `judge` block names the route that answered. When the fallback fails too, the error carries both causes. Setting `judge.route: minimax:key` is your choice, like any route; `check --judge minimax:key` tries it for one call.
476
+ - `minimax:key`: a key-only route on your MiniMax key, with no agent CLI. done-done reads `MINIMAX_API_KEY` and hands it only to the kit's Anthropic backend, which done-done points at MiniMax's Anthropic-compatible endpoint (`https://api.minimax.io/anthropic/v1`, the only address it will send the key to). `judge.model` is optional: empty runs `MiniMax-M3.1-Flash-Preview`, which reads images. With no explicit `judge.fallback_route`, this route retains its built-in fallback: when its call fails (a refused or missing key, a rate limit, no answer), `check`, `receipt` and `verify` judge the whole check again on `claude-code` at claude's cheap model (`agent_config.claude.model`, else the default), and the result's `judge` block names the route that answered. When the fallback fails too, the error carries both causes. Setting `judge.route: minimax:key` is your choice, like any route; `check --judge minimax:key` tries it for one call.
477
+
478
+ **Optional second route.** Global `judge.fallback_route` accepts the same routes as `judge.route` and is unset by default. For `claude-code`, a rate/usage limit, missing sign-in, or an account picker returning no eligible account triggers one retry of the **whole check** on that route. Invalid picker JSON/configuration, malformed model answers and other Claude errors still stop the call. With no fallback configured, Claude failures keep their existing error and no verdict. Other routes retry on their existing judge errors; an explicit fallback replaces MiniMax's built-in fallback. There is only one hop (no chains, and the same route is not retried). The result's `judge.route`, `model` and fallback `detail` name the judge that answered; if both fail, both causes are reported. The fallback uses its own `agent_config.<agent>.model` or default, **not** the primary's `judge.model`. Key routes therefore need their own default model to work as a fallback (`minimax:key` has one; `openai:key` does not).
479
+
480
+ For Codex/Sol on the already signed-in Pi CLI (no API key, paid-key route or new login):
481
+
482
+ ```yaml
483
+ agent_config:
484
+ pi:
485
+ model: openai-codex/gpt-6.1-sol
486
+ judge:
487
+ route: claude-code
488
+ fallback_route: answerer:pi
489
+ ```
467
490
 
468
491
  A provider key for an `answerer:<agent>` judge, or `OPENAI_API_KEY` / `MINIMAX_API_KEY` for `openai:key` / `minimax:key`, is read from the environment of whoever runs done-done. To work in every shell, point `judge.env_file` (global config) at a file of `KEY=value` lines (`export` and quotes allowed); its names that the shell has not exported go to the judge's call only. done-done never writes a key into a config file and never prints one; `init --judge` on its own changes only the global config, in any directory.
469
492
 
@@ -544,6 +567,22 @@ After those checks pass, each live image is sent beside its approved mock throug
544
567
 
545
568
  With no manifest, claims behave as before. `design` and `look --mock` remain ad-hoc measurements of supplied screens, not coverage claims: use `check`, `receipt` or `verify` to prove the complete approved inventory. This checks the declared inventory; it cannot discover an approved shot omitted from the manifest or establish capture authenticity beyond the existing run record.
546
569
 
570
+ #### Assets, colors, interactions and unlisted references
571
+
572
+ Screens are not the whole approval. Name every other approved item in `approved_requirements`, each with the live evidence it needs, or skip it with an explicit exclusion:
573
+
574
+ ```yaml
575
+ approved_requirements:
576
+ - { kind: asset, name: logo, capture: logo.png, mock: design/logo.png } # judged beside its approved image
577
+ - { kind: color, name: brand-accent, capture: accent.png } # must be cited live evidence
578
+ - { kind: interaction, name: swipe-to-delete, capture: swipe.png }
579
+ - { kind: reference, name: design/spare.png, exclusion: owner dropped the spare illustration }
580
+ ```
581
+
582
+ `kind` is `asset`, `color`, `interaction` or `reference`. A row needs a unique `capture` filename (one cited, run-bound PNG/JPEG; optional `mock` sends it through the design judge) **or** `exclusion: <reason>`, the only way to skip it. An uncovered row fails the claim before any judge call, naming the item. Exclusions are reported under `approved_exclusions`. Receipts do not yet bind requirement captures; they are enforced by `check`, `receipt` and `verify` at run time.
583
+
584
+ **Behavior that tightens.** (1) Committed PNG/JPEG files under a top-level `design*/`, `mock*/`, `approved*/` or `prototype*/` folder that no `approved_design` `mock`, requirement `mock` or `kind: reference` row names fail every claim as "approved reference material is not in the approved_design manifest". (2) A claim whose outcome mentions design, mock, prototype or approved, with no `approved_design` manifest, no requirement `mock` and no `base_ref` baseline capture, fails with `not compared to approved result`; render, fit and dark-mode checks alone do not prove it. Manifests that list every reference and compare against it keep working unchanged.
585
+
547
586
  #### Interaction checklist (opt-in)
548
587
 
549
588
  Visual comparisons do not prove interaction polish. The package ships six requirements in `skills/done-done/interaction-checklist.json`: `detail-navigation` (next/previous **and swipe**), `instant-state` (immediate feedback on every send/answer), `destructive-undo` (undo restores content), `scroll-restoration` (back keeps list position), `list-search` (search/filter on long lists), and `empty-next-step` (empty states explain what to do next).
@@ -751,13 +790,14 @@ evidence:
751
790
  local_root: ~/.done-done/evidence # evidence root for repos that set none (<local_root>/<app>)
752
791
  judge: # done-done's own judge; check, receipt, verify and design always start it
753
792
  # route: claude-code # claude-code, answerer:<agent> (opencode, pi, gemini) openai:key (reads OPENAI_API_KEY) or minimax:key (reads MINIMAX_API_KEY, claude-code fallback), set by you (`init --judge` saves the first three; a key route is set by hand) and unset until then
793
+ # fallback_route: answerer:pi # optional; its agent_config.pi.model can be openai-codex/gpt-6.1-sol
754
794
  model: '' # default: that agent's agent_config model, or openai-codex/gpt-6-luna for pi
755
795
  floor: 0.7 # every judge's pick must reach this probability to count; below it the verdict is unsure (inconclusive), never a pass
756
796
  # env_file: ~/.config/done-done/judge.env # KEY=value lines (a provider API key) handed to the judge for names your shell has not exported; the key stays in that file
757
797
  # account_command: account-picker pick claude --json # optional shell command for Claude judge and agent calls (verify/maintain too), once per process; exported CLAUDE_CONFIG_DIR wins
758
798
  ```
759
799
 
760
- `judge.account_command` must print one JSON object: `{"ok":true,"id":"pool-1","set":{"CLAUDE_CONFIG_DIR":"/absolute/existing/folder"},"unset":["ANTHROPIC_API_KEY"]}`. Only the selected folder is set, and each `unset` name is deleted from the spawned environment; it cannot unset `CLAUDE_CONFIG_DIR`. The detail names only the account id. A failed or invalid pick stops the call with its cause and no verdict, never falling back to the default login. Unset, it preserves normal Claude sign-in behavior; other agents and API-key routes do not run it.
800
+ `judge.account_command` must print one JSON object: `{"ok":true,"id":"pool-1","set":{"CLAUDE_CONFIG_DIR":"/absolute/existing/folder"},"unset":["ANTHROPIC_API_KEY"]}`. Only the selected folder is set, and each `unset` name is deleted from the spawned environment; it cannot unset `CLAUDE_CONFIG_DIR`. The detail names only the account id. A pick returning `ok: false` (or JSON `null`) is an unavailable Claude route and uses `judge.fallback_route` when configured; otherwise it stops with the same cause and no verdict. An invalid pick still stops the call. Neither case falls back to the default Claude login. Unset, it preserves normal Claude sign-in behavior; other agents and API-key routes do not run it.
761
801
 
762
802
  An `agent_config` entry merges with the defaults field by field, so `claude: { model: claude-sonnet-5-5 }` keeps `effort: low`; the other maps merge entry by entry. `done-done doctor` shows which file set each agent setting.
763
803
 
@@ -778,6 +818,7 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
778
818
  | `dom` | none; page-check records per capture, see [DOM assertions](#dom-assertions) |
779
819
  | `look` | none; the look step's approved design reference (`tokens`, `max_control`), see [Look check](#look-check) |
780
820
  | `interaction_checklist` | none; opt-in six-item interaction evidence map; omitted/unproven items flagged, absent surfaces need reasons; see [Interaction checklist](#interaction-checklist-opt-in) |
821
+ | `approved_requirements` | none; approved assets, colors and interactions with the live capture each needs, or an explicit `exclusion`, see [Assets, colors, interactions](#assets-colors-interactions-and-unlisted-references) |
781
822
  | `approved_design` | none; committed screen × pixel size × theme manifest of mocks and required live filenames; gates all claims, see [Approved-design coverage](#approved-design-coverage) |
782
823
  | `inputs` | `[]`; repo-relative paths or globs the drive must leave alone, see [Input guard](#input-guard) |
783
824
  | `base_ref` | none; a ref `verify` also drives, pairing its captures with the head's, see [Base-ref before](#base-ref-before) |
@@ -798,3 +839,23 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
798
839
 
799
840
  Global-only keys describe the machine. Project-only keys describe the project. Setting a global-only or project-only key in the wrong file is an error (exit 2), not something silently ignored.
800
841
 
842
+
843
+ ## Working on done-done
844
+
845
+ ```sh
846
+ npm install -g --prefix ~/.local . # optional: links this checkout as done-done on your PATH
847
+ npm run build:skill # regenerate skills/done-done/SKILL.md from src/skill.ts
848
+ npm run build:skill -- --check # fail if the committed skill is out of date
849
+ ```
850
+
851
+ To try an unreleased commit without cloning (npm 12 needs `--allow-git=all`):
852
+
853
+ ```sh
854
+ npx -y --allow-git=all github:umeranjum17/done-done-axi --help
855
+ ```
856
+
857
+ `dist/` is committed, so installing from GitHub needs no build step. `npm test` rebuilds it and fails while the rebuilt `dist/` differs from the commit or has files git does not track, so `git add` the new `dist/` files with your change.
858
+
859
+ The first-screen terminal images (`docs/media/hero-*.png`, and the animated `hero.gif`) are real output from the published CLI, captured in a sample repo. `node docs/media/hero.mjs` rebuilds their pages from the captured transcripts beside it (`hero-run.txt`, `hero-refuse.txt`) and fails when a shown value is not in them; screenshot `.hero/hero-desktop.html` (828x596) and `.hero/hero-phone.html` (418x792) at device scale factor 2, in light and dark. The GIF replays the same lines a step at a time on that page, ending on the judge's pass and the receipt. The four lower terminal illustrations (`hero`, `run`, `pass`, `reasons`, `config`) use a stub judge, so no model runs; redraw them with `npx tsc && node docs/media/make.mjs`. The logo (`docs/logo.svg`) and the flow picture are hand-made SVGs.
860
+
861
+ The demo notes app behind those pictures: start one with `node docs/media/demo.mjs <fresh-scratch-directory> 0` (the server prints its owned port), open a native run in its `app/` repo, drive the checklist in the browser, save `app.png` at 400×140 DPR 2 into the run's evidence folder, seal it and run `check` with your configured judge. Save the open result as `<scratch>/open.json` and the check result as `<scratch>/check.json`; `/proof` displays that screenshot and verdict and refuses to show a pass without a real passing result. Capture `/proof` at 400×230 DPR 2 with `chrome-devtools-axi`, keep the scratch outside the repo and stop the server afterwards.
package/docs/releasing.md CHANGED
@@ -1,5 +1,5 @@
1
- # 0.6.11 patch release
1
+ # 0.6.13 patch release
2
2
 
3
- This release shows the running version in CLI help headers, labels prereleases without changing the bare `--version` line, and clarifies the signoff posting hint.
3
+ This release carries the rewritten README to the npm page: a real judged run on the first screen, the hero images rendered from captured CLI transcripts, and reference detail moved into the docs.
4
4
 
5
- Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.11 version dist.integrity` and smoke-test a clean `npx` install with `done-done --version`. Do not run `npm publish` manually unless the workflow fails and the release is blocked; if so, stop and report.
5
+ Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.13 version dist.integrity` and smoke-test a clean `npx` install with `done-done --version`. Do not run `npm publish` manually unless the workflow fails and the release is blocked; if so, stop and report.
Binary file
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "done-done-axi",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
4
4
  "license": "Apache-2.0",
5
- "description": "Proof that the real product works before a task is called done: commit-bound evidence runs and evidence checks.",
5
+ "description": "Prove your app really works: done-done drives your app the way a user does, a fresh judge checks every capture, and the proof is tied to a commit.",
6
6
  "type": "module",
7
7
  "repository": { "type": "git", "url": "git+https://github.com/umeranjum17/done-done-axi.git" },
8
- "homepage": "https://github.com/umeranjum17/done-done-axi#readme",
8
+ "homepage": "https://umeranjum17.github.io/done-done-axi/",
9
9
  "bugs": { "url": "https://github.com/umeranjum17/done-done-axi/issues" },
10
- "keywords": ["axi", "done", "proof", "evidence", "screenshot", "verification", "cli", "agent"],
10
+ "keywords": ["axi", "done", "proof", "evidence", "screenshot", "verification", "cli", "agent", "ai-agents", "coding-agents"],
11
11
  "bin": { "done-done-axi": "dist/cli.js", "done-done": "dist/cli.js" },
12
12
  "files": ["dist", "!dist/skill.js", "skills", "docs", "integrations"],
13
13
  "engines": { "node": ">=22.18" },
@@ -98,6 +98,9 @@ fresh session of its own, looks at each capture. `check` and `receipt` start the
98
98
  and nothing turns it off: there is no result without it. When it says no judge is set, ask the user which
99
99
  judge (and model) to use and save their answer with the command it prints; never choose one for them. When
100
100
  the judge fails, report its error to the user; never call the work proven without a judge pass.
101
+ An explicitly configured `judge.fallback_route` can judge when Claude has no eligible account, sign-in or usage left;
102
+ `answerer:pi` with `agent_config.pi.model: openai-codex/gpt-6.1-sol` uses the existing Codex login.
103
+ Read `done-done --help` for fallback configuration; results name the route that actually answered.
101
104
 
102
105
  ## Request
103
106