done-done-axi 0.6.0 → 0.6.2

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
@@ -1,22 +1,21 @@
1
+ <a id="done-done"></a>
1
2
  <div align="center">
2
3
 
3
- <img src="docs/logo.svg" width="72" height="72" alt="done-done logo">
4
+ <img src="docs/logo.svg" width="64" height="64" alt="done-done">
4
5
 
5
- # done-done
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)
6
7
 
7
- ### No proof, no "done".
8
-
9
- **[Landing page](https://umeranjum17.github.io/done-done-axi/)**
8
+ </div>
10
9
 
11
- done-done won't accept "done" without real, fresh proof from your product, captured at the exact commit being called done.
10
+ # Prove your app really works.
12
11
 
13
- [![Node 22.18+](https://img.shields.io/badge/node-22.18%2B-3fb950?logo=nodedotjs&logoColor=white)](https://nodejs.org) [![Status: early preview](https://img.shields.io/badge/status-early%20preview-d29922)](#whats-next) [![npm: done-done-axi](https://img.shields.io/npm/v/done-done-axi?logo=npm&logoColor=white&label=npm)](https://www.npmjs.com/package/done-done-axi)
12
+ A command-line check of real screenshots, recordings and output.
14
13
 
15
- </div>
14
+ For developers using coding agents. Tests can pass while the app is still broken.
16
15
 
17
- ## Install in 3 steps
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.">
18
17
 
19
- You need Node 22.18 or later and git.
18
+ <sub>Real sample app + live judge verdict, at the captured commit.</sub>
20
19
 
21
20
  ```sh
22
21
  npm install -g done-done-axi
@@ -24,31 +23,29 @@ cd your-app && done-done init
24
23
  done-done doctor
25
24
  ```
26
25
 
27
- 1. **Install.** This 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`.
28
- 2. **Set up your repo.** `init` writes a minimal `.done-done.yaml` and asks you, in a terminal, which coding agent and which judge to use. done-done never picks either for you. Outside a terminal, pass `--agent <agent> --model <model> --judge <agent> --judge-model <model>` instead.
29
- 3. **Check the setup.** `doctor` changes nothing and names each missing piece with the exact command that fixes it.
26
+ [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
27
 
31
- Then prove something: `done-done verify --outcome "<what a user can now do>"`. [Manual CLI](#manual-cli) below walks through journeys, `check` and `receipt`.
28
+ ## Install in 3 steps
32
29
 
33
- <div align="center">
30
+ 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).
34
31
 
35
- <picture>
36
- <source media="(prefers-color-scheme: dark)" srcset="docs/media/flow-dark.svg">
37
- <img src="docs/media/flow-light.svg" alt="How it works: capture your real app, tie every file to the exact commit, then check: pass with exit 0 or fail with exit 1." width="710">
38
- </picture>
32
+ 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`.
33
+ 2. **Set up your repo.** `init` writes a minimal `.done-done.yaml` and asks you, in a terminal, which coding agent and which judge to use. done-done never picks either for you. Outside a terminal, pass `--agent <agent> --model <model> --judge <agent> --judge-model <model>` instead.
34
+ 3. **Check the setup.** `doctor` changes nothing and names each missing piece with the exact command that fixes it. It checks setup, not whether your app works.
39
35
 
40
- <br><br>
36
+ Review and commit the capture recipe and verify skill, then ask for proof:
41
37
 
42
- <picture>
43
- <source media="(prefers-color-scheme: dark)" srcset="docs/media/hero-dark.svg">
44
- <img src="docs/media/hero-light.svg" alt="A terminal: done-done records a run of a demo notes app and the check passes. After a code change, the same check fails because the proof is for an older commit." width="560">
45
- </picture>
38
+ ```sh
39
+ done-done verify --outcome "<what a user can now do>"
40
+ ```
46
41
 
47
- </div>
42
+ Your agent drives the real product and saves screenshots or output. A fresh judge checks whether each capture shows your claim. No judge pass, no receipt. 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.
48
43
 
49
44
  ## Why
50
45
 
51
- "Done" is easy to say. Screenshots get reused, recordings go stale, and a green test run is not the app.
46
+ <a id="no-proof-no-done"></a>
47
+
48
+ **No proof, no "done".** "Done" is easy to say. Screenshots get reused, recordings go stale, and a green test run is not the app.
52
49
 
53
50
  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.
54
51
 
@@ -103,77 +100,7 @@ The skill is the recommended path, but it is not the only one.
103
100
 
104
101
  ### Manual CLI
105
102
 
106
- **1. Install**
107
-
108
- ```sh
109
- npm install -g --prefix ~/.local done-done-axi
110
- ```
111
-
112
- `--prefix ~/.local` installs into your home folder, so it needs no `sudo`. This installs two names for the same CLI: `done-done-axi` and the short `done-done` used below. If `command -v done-done` prints nothing, add `~/.local/bin` to your `PATH`: `export PATH="$HOME/.local/bin:$PATH"`.
113
-
114
- To skip the install, run `npx -y done-done-axi` wherever this README says `done-done`.
115
-
116
- **2. Set it up in your app's repo**
117
-
118
- ```sh
119
- cd your-app
120
- done-done init
121
- ```
122
-
123
- `init` writes a minimal `.done-done.yaml` (only if you don't already have one) that names your app, and a commented global `~/.done-done/config.yaml` (only if there is none). done-done never picks the coding agent it starts: `init` asks you in a terminal, or takes `--agent <agent> --model <model>`. It never picks the judge either, the agent of its own that `check`, `receipt`, `verify` and `design` always start to look at each capture: `init` asks in a terminal, or takes `--judge <agent> --judge-model <model>`. Any agent and model you choose runs, your coding agent's own too (the judge is a fresh session of its own). The config keys are in the [reference](docs/reference.md#config). A plain `init` never overwrites an existing config. When your coding agent is set, `init` also has it write a verify skill, `.agents/skills/done-done-verify-<app>/SKILL.md`, from the repo's own files (never overwriting one; with no agent set, no skill is written; an older `verify-<app>` skill is renamed to this once). Check its launch never touches real accounts or your own state. Evidence lives outside the repo, in `~/.done-done/evidence/<app>`, so it outlives the checkout that captured it. For an `answerer:pi` judge with no explicit model, done-done uses `openai-codex/gpt-6-luna`; `judge.model` or `agent_config.pi.model` overrides it. Then describe how to capture proof — swap the command for whatever drives your real app the way a user does and saves its screenshots, recordings or output (here a CLI's own output):
124
-
125
- ```yaml
126
- journeys:
127
- smoke:
128
- command: npm start > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log && node record-steps.mjs
129
- core: true
130
- ```
131
-
132
- The command runs from the repo root with `DONE_DONE_EVIDENCE_ROOT` (a new folder for this run in the evidence root) and `DONE_DONE_RUN_ID` set, and `${RUN_ID}` in it is replaced. A run that writes nothing into its folder fails. The command also writes `steps.json` beside its captures: the target version, the state it started from, the steps a user takes and the result a user sees. `check`, `receipt` and `gate` read the claim against that record, so a test log wrapped in a script cannot prove a claim about the product ([the record](docs/reference.md#step-record)). A journey that only runs code checks (`npm test`, `yarn run check`, `tsc`, `pytest`, a linter or a build) is code-only: its log says the code checks pass, not that the product works, so `init --journey` refuses it and `check`, `receipt` and `gate` never count it as proof ([the rule](docs/reference.md#real-product-evidence)).
133
-
134
- Name your **core journey** with `core: true`: the one thing a user cannot live without, driven end to end on the real target (for a remote terminal app: pair, open a pane, type, see the output). If your app ships something users install and upgrade (an npm CLI, an Android app, a host service), also record an **upgrade journey** with `upgrade: true` that installs your previous release, upgrades over it in place, then drives the core journey: a fresh install alone is not how users get your release. `doctor` fails until both are named ([the rule](docs/reference.md#core-journey-and-release-path)).
135
-
136
- Optional: `done-done init --hook <claude|codex|opencode|gemini|pi>` adds a session hook or plugin to the repo that shows each session the bare `done-done` state. Each agent's file and behavior are in the [reference](docs/reference.md#set-up-a-repo).
137
-
138
- Record the recipe in the repo so anyone can re-run the proof, then commit it (and `.claude/settings.json`, `.codex/hooks.json`, `.gemini/settings.json`, `.opencode/plugins/done-done.js` or `.pi/extensions/done-done.js` if you added a hook):
139
-
140
- ```sh
141
- done-done init --journey smoke --core -- 'npm start > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log && node record-steps.mjs'
142
- git add .done-done.yaml
143
- git commit -m "Add done-done"
144
- ```
145
-
146
- A `run -- <command>` whose command matches no recorded journey still runs, but its result hints that the recipe is not in the repo. For a proof you drive by hand or by your agent instead of a journey, `done-done run --open` prints a run id and a folder to capture into, and `done-done run --close <run-id>` seals it ([how](docs/reference.md#run-a-journey)).
147
-
148
- At any point, `done-done doctor` checks the setup and changes nothing except the `--probe` call record. Each item reports `ok`, `warning` or `missing` with the exact fix command; an agent you have not set yet is `missing`, with the command that saves your choice, and so is a config whose journeys only run code checks, one that names no core journey, or an app that ships an installable artifact with no upgrade journey. `done-done doctor --probe` also asks the agent one question to prove it answers; on a new machine that probe fails until the agent is signed in (for pi, run `pi` and then `/login`). The quick start above never calls the coding agent, so `run` and `check` work before it is signed in; `check` does call the judge, so set one first. The items are listed in the [reference](docs/reference.md#check-the-setup).
149
-
150
- `done-done maintain` keeps the verify skill true: it checks the skill's links and feature index, checks that every `file:line` it cites exists, and has your coding agent read the source for each feature, read-only, to report drift. It ends `clean`, `drift` with the findings to fix, or `blocked`. It never runs without that reader: an agent that is not set asks, and one that cannot start or cannot read without tools stops it before the run (exit 2). With `auto_fix: { maintain: <n> }` (as in no-mistakes; 0, the default, reports only), your judge first sorts each drift finding (a skill out of date, or a product that may be wrong, which stops and asks you), then your agent fixes the skill folder only, in a done-done worktree, and maintain runs again, up to n times. A clean end is `changed`: one commit on a `done-done/maintain-<run-id>` branch, your branch untouched ([reference](docs/reference.md#keep-the-verify-skill-true)).
151
-
152
- **3. Run it, then check it**
153
-
154
- ```sh
155
- done-done run smoke
156
- done-done check --outcome "it starts" ~/.done-done/evidence/your-app/*/*.log
157
- ```
158
-
159
- Each command prints one machine-readable result. By default it is [TOON](https://github.com/toon-format/spec) (the compact AXI convention). `run` shows `result: recorded`: it captured evidence, and says nothing about whether it proves the claim. `check` starts your judge on each capture, and only a check the judge passes shows `result: pass`; with no judge set it asks for one, and a judge that fails stops with the cause (exit 2), never a result. Add `--json` for JSON, or `--full` to list every evidence file or finding instead of the first few. After later runs, use the `run_id` the result prints in place of the first `*`.
160
-
161
- Not sure which journeys a change touches? `done-done affected` lists them from the verify skill's feature map and the paths each feature cites (the core journey when it cannot tell, never fewer), and exits 1 until a receipt for HEAD covers them. Bare `done-done`, which the session hooks run, names them too.
162
-
163
- When a check passes, save a receipt and re-verify it later:
164
-
165
- ```sh
166
- done-done receipt --outcome "it starts" ~/.done-done/evidence/your-app/*/*.log
167
- done-done receipt --verify "$(git rev-parse HEAD)"
168
- ```
169
-
170
- No journey yet? `done-done verify --outcome "<what a user can now do>"` has your coding agent drive one feature of the verify skill the way a user does (`--feature <name>` picks it). Your judge then checks the captures. When it says a capture is wrong or unclear, or a capture it cannot read (a video or other file) is not judged, the run waits at its gate (exit 4) until you answer with `done-done respond --action fix`, which ends it `fail` so you fix the product or the recipe and run again: nothing moves a run past its judge, and no receipt is written without a judge pass. Until then, a new `verify` or `rerun` of the same claim in that checkout is refused, so nothing goes around the gate ([details](docs/reference.md#drive-by-agent)).
171
-
172
- With `auto_fix: { verify: <n> }` (0, the default, reports only), a failed verify is fixed with your own agent: your judge first sorts each wrong screen (a product bug to fix, or a question that waits at the gate for you), then your agent fixes the product in a done-done worktree with your ignored files copied in, and verify runs again there, up to n times. A pass ends `changed`, with the fix on a `done-done/verify-<run-id>` branch and your branch untouched ([details](docs/reference.md#fix-the-product)).
173
-
174
- 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.
175
-
176
- Run `done-done` with no arguments to see the current config and the next step.
103
+ 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).
177
104
 
178
105
  ## What it checks
179
106
 
@@ -199,7 +126,7 @@ Every answer is an exit code: **0** pass, **1** fail, **2** usage or config erro
199
126
 
200
127
  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)):
201
128
 
202
- - [`sign-off-axi`](https://www.npmjs.com/package/sign-off-axi): the board where agents ask you questions. `done-done signoff` writes a run's gate (when the judge was unsure) or its evidence in sign-off's message format and prints the post command; it never posts. Without sign-off you answer at the terminal with `done-done respond`.
129
+ - [`sign-off-axi`](https://www.npmjs.com/package/sign-off-axi): the board where agents ask you questions. `done-done signoff` writes a run's gate (when the judge was unsure) or its evidence in sign-off's message format and prints the post command; it never posts on its own. With the opt-in (`done-done signoff --post`, or `signoff.post: true` in `.done-done.yaml`) it posts that message to the board through sign-off-axi, records the card on the run, and `done-done signoff --wait` brings the answer back to the same run. Without sign-off you answer at the terminal with `done-done respond`.
203
130
  - [`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.
204
131
  - [`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.
205
132
 
@@ -255,6 +182,6 @@ npx -y --allow-git=all github:umeranjum17/done-done-axi --help
255
182
 
256
183
  `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.
257
184
 
258
- The terminal pictures (hero, run, pass, reasons, config) are real output from the current build, with 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.
185
+ 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.
259
186
 
260
187
  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/cli.js CHANGED
@@ -10,7 +10,7 @@ import { parseArgs } from 'node:util';
10
10
  import { encode } from '@toon-format/toon';
11
11
  import { Document, parseDocument } from 'yaml';
12
12
  import { ACPX_INSTALL, agentChoices, agentPath, agentRun, askAgent, editOnlyReason, findBin, JUDGES, modelFor, noAgent, noJudge, notStartable, routeOf, selectAgent, selectFallback, selectJudge, toollessReason } from './agents.js';
13
- import { AGENTS, CHEAP, globalConfigText, home, inputPattern, isAgent, isJudgeRoute, loadConfig, parseDuration, UsageError } from './config.js';
13
+ import { AGENTS, CHEAP, globalConfigText, home, inputPattern, isAgent, isJudgeRoute, loadConfig, parseDuration, tilde, UsageError } from './config.js';
14
14
  import { backend, judge, judgeable, onPath, quote, screen } from './judge.js';
15
15
  import { startInstance, stopInstance } from './instance.js';
16
16
  import { attemptProblem, changedSince, commitFix, dirtyTracked, FIX_SCHEMA, inWorktree, maintainSteps, named, pathspec, readerProblem, repoState, triage } from './maintain.js';
@@ -60,11 +60,17 @@ const RUNS_TEXT = `runs: each verify or maintain run lives in $DONE_DONE_HOME/ru
60
60
  hashed before and after the drive; the check step fails naming each changed, appeared or
61
61
  disappeared path (never file contents), and the before/after hash lists go into the run record; no key: unchanged
62
62
  gate: a judge fail or unsure (or an agent drive with no judged capture) parks the run as awaiting_approval (exit 4); nothing moves it
63
- past the judge: respond --action fix ends it fail, with no receipt (approve and skip answer only a spend gate)
63
+ past the judge: respond --action fix ends it fail, with no receipt (skip ends it fail too, without the fix; approve answers only a spend gate)
64
64
  auto_fix: { verify: <n> } (config; 0, the default, reports only): the judge sorts each wrong screen (fix or ask; an ask, an unsure
65
65
  answer or a mixed result parks at the gate), then the agent the user set fixes the product in a done-done worktree with your
66
66
  ignored files copied in, and verify runs again there, up to n times. Result: changed (the fix on a done-done/verify-<run-id>
67
67
  branch, your HEAD still fails; exit 1) or blocked (dirty checkout, a guard, or every attempt used; exit 1)`;
68
+ const SIGNOFF_TEXT = `sign-off (.done-done.yaml): the board where the person answers. done-done writes the message (contract v1) and, with the opt-in, posts it:
69
+ signoff.post: true lets every signoff post to the board; --post posts one run. Without it, signoff only writes the message and prints the post command.
70
+ signoff.board names the board file (default: the nearest .sign-off/ board sign-off-axi finds); --board names it for one run.
71
+ signoff --wait waits for the answer to the card that run posted (--timeout, default 10m) and feeds it back to that run: a gate's
72
+ approve, skip or fix ends or resumes the run, exactly as done-done respond does. The card (its key and board) is recorded on the run,
73
+ so status shows it. The board is sign-off's own: done-done posts through sign-off-axi and never edits a board.`;
68
74
  const MAINTAIN_TEXT = `maintain reads the verify skill and the source; HEAD, git status and the content of changed files must be the same before and after.
69
75
  locate: the verify skill (.agents/skills/done-done-verify-<app>/SKILL.md); none: blocked, create it with done-done init
70
76
  hygiene: every local link in the skill resolves; features/README.md lists each features/*.md file once
@@ -124,8 +130,11 @@ const FLAGS = {
124
130
  '--mock <screen>=<mock>': 'judge this screen next to its approved mock (repeat per screen; the screen is judged even when not listed)',
125
131
  '--step <name>': 'the step log to show (verify: resolve, drive, check, judge, gate, receipt, report; maintain: locate, hygiene, source-wave, reconcile, triage, fix, report)',
126
132
  '--feature <name>': 'with no journey, the author agent drives this feature of the verify skill (features/<name>.md); default: the only feature, else the agent picks the one the outcome is about',
127
- '--action <approve|skip|fix>': 'at a spend gate, approve: spend and run the journey; skip: end the run with nothing spent. At the judge gate only fix: end it fail, then fix the product or the verify skill (done-done maintain checks it against the source)',
133
+ '--action <approve|skip|fix>': 'at a spend gate, approve: spend and run the journey; skip: end the run with nothing spent. At the judge gate only fix or skip: fix ends it fail, then the product or the verify skill is fixed (done-done maintain checks it against the source); skip ends it fail without the fix',
128
134
  '--reason "<why>"': 'why (required for approve and skip at a spend gate); kept in the run with who answered',
135
+ '--post': 'with signoff: post the message to the sign-off board through sign-off-axi and record the card on the run (the opt-in: without it, signoff only writes the message)',
136
+ '--board <file>': 'with signoff: the board file to post to (default: the nearest .sign-off/ board sign-off-axi finds)',
137
+ '--timeout <duration>': 'with signoff --wait: how long to wait for the answer (default 10m)',
129
138
  '--limit <n>': 'how many past runs to list (default all)',
130
139
  '--out <file.png>': 'where look writes the screenshot; its page-check record goes next to it as <file>.dom.json (default: look.png in $DONE_DONE_EVIDENCE_ROOT inside a journey, else in the current folder)',
131
140
  '--agent <agent>': 'the coding agent done-done starts (claude, codex, opencode, pi, gemini or acp:<target>), saved as agent in ~/.done-done/config.yaml; without it, init asks in a terminal',
@@ -289,9 +298,13 @@ const COMMANDS = {
289
298
  more: [RUNS_TEXT],
290
299
  },
291
300
  signoff: {
292
- usage: [['done-done signoff [--run <run-id>]', "write the run's sign-off message (contract v1): a gate for a run waiting at its gate, evidence for a finished run; it prints the post command and never posts"]],
293
- flags: ['--run <run-id>'],
294
- more: [RUNS_TEXT],
301
+ usage: [
302
+ ['done-done signoff [--run <run-id>]', "write the run's sign-off message (contract v1): a gate for a run waiting at its gate, evidence for a finished run; without the opt-in it prints the post command and never posts"],
303
+ ['done-done signoff --post [--board <file>]', 'post that message to the sign-off board through sign-off-axi and record the card on the run'],
304
+ ['done-done signoff --wait [--timeout <duration>]', 'wait for the answer to the card this run posted and feed it back to that run'],
305
+ ],
306
+ flags: ['--run <run-id>', '--post', '--board <file>', '--wait', '--timeout <duration>'],
307
+ more: [RUNS_TEXT, SIGNOFF_TEXT],
295
308
  },
296
309
  history: {
297
310
  usage: [['done-done history [--limit <n>]', 'list past runs, newest first']],
@@ -1949,6 +1962,12 @@ const reapJourney = (dir, run) => {
1949
1962
  if (run.runner?.instance_pgid && createOnce(join(dir, 'instance.killed')))
1950
1963
  killGroup(run.runner.instance_pgid);
1951
1964
  };
1965
+ // The exit code a run's state means: 0 pass, 1 fail, 2 stopped, 3 inconclusive, 4 still running or waiting at its gate.
1966
+ const runCode = (run, status) => status === 'pending' || status === 'running' || status === 'awaiting_approval' ? WAITING
1967
+ : run.stopped && status === 'failed' ? USAGE
1968
+ : status === 'cancelled' || status === 'failed' || run.result === 'fail' || run.result === 'drift' || (run.kind === 'verify' && (run.result === 'changed' || run.result === 'blocked')) ? FAIL
1969
+ : run.result === 'inconclusive' || run.result === 'blocked' ? INCONCLUSIVE
1970
+ : PASS;
1952
1971
  // One run as status shows it. A run still marked running whose runner is gone is shown as failed at its step.
1953
1972
  // `gone`: the caller saw its runner child exit, which also covers a runner that died before it wrote its pid.
1954
1973
  function showRun(dir, cfg, command, gone = false) {
@@ -1963,7 +1982,7 @@ function showRun(dir, cfg, command, gone = false) {
1963
1982
  reason = `the runner died at step ${at?.name}${run.runner ? ` (pid ${run.runner.pid}, last heartbeat ${beat}s ago)` : ' before it started'}`;
1964
1983
  reapJourney(dir, run);
1965
1984
  }
1966
- const code = status === 'pending' || status === 'running' || status === 'awaiting_approval' ? WAITING : run.stopped && status === 'failed' ? USAGE : status === 'cancelled' || status === 'failed' || run.result === 'fail' || run.result === 'drift' || (run.kind === 'verify' && (run.result === 'changed' || run.result === 'blocked')) ? FAIL : run.result === 'inconclusive' || run.result === 'blocked' ? INCONCLUSIVE : PASS;
1985
+ const code = runCode(run, status);
1967
1986
  const drive = run.journey ?? (run.feature ? `--feature ${arg(run.feature)}` : '');
1968
1987
  const again = run.kind === 'maintain' ? 'done-done maintain' : `done-done verify ${drive ? `${drive} ` : ''}--outcome ${arg(run.claim)}${run.claim_id ? ` --claim-id ${arg(run.claim_id)}` : ''}`;
1969
1988
  const findings = cap(run.findings ?? []);
@@ -1987,6 +2006,8 @@ function showRun(dir, cfg, command, gone = false) {
1987
2006
  result.response = run.response;
1988
2007
  if (run.spend)
1989
2008
  result.spend = run.spend;
2009
+ if (run.signoff)
2010
+ result.signoff = run.signoff;
1990
2011
  if (ev.items.length)
1991
2012
  result.evidence = ev.items;
1992
2013
  if (ev.omitted)
@@ -2134,7 +2155,8 @@ async function cmdVerify(argv) {
2134
2155
  }
2135
2156
  // Run the steps in this process (--foreground), or in a detached runner with no pipe back to this process, so the
2136
2157
  // runner goes on when this command or its shell ends; --wait (the default) holds this command until the run ends.
2137
- async function launch(dir, cfg, command, how) {
2158
+ // Start the run's runner and wait for it to end; the caller shows the run.
2159
+ async function startRunner(dir, how) {
2138
2160
  if (how.foreground) {
2139
2161
  const stop = heartbeat(dir);
2140
2162
  try {
@@ -2143,7 +2165,7 @@ async function launch(dir, cfg, command, how) {
2143
2165
  finally {
2144
2166
  stop();
2145
2167
  }
2146
- return showRun(dir, cfg, command);
2168
+ return { gone: false };
2147
2169
  }
2148
2170
  const fd = openSync(join(dir, 'logs', 'runner.log'), 'a');
2149
2171
  const child = spawn(process.execPath, [CLI_PATH, '__runner', dir], { cwd: loadRun(dir).repo, detached: true, stdio: ['ignore', fd, fd] });
@@ -2151,7 +2173,11 @@ async function launch(dir, cfg, command, how) {
2151
2173
  closeSync(fd);
2152
2174
  if (!how['no-wait'])
2153
2175
  await waitRun(dir, child.pid);
2154
- return showRun(dir, cfg, command, !alive(child.pid));
2176
+ return { gone: !alive(child.pid) };
2177
+ }
2178
+ async function launch(dir, cfg, command, how) {
2179
+ const { gone } = await startRunner(dir, how);
2180
+ return showRun(dir, cfg, command, gone);
2155
2181
  }
2156
2182
  async function cmdMaintain(argv) {
2157
2183
  const { values } = parseArgs({ args: argv, options: { wait: { type: 'boolean' }, 'no-wait': { type: 'boolean' }, foreground: { type: 'boolean' } } });
@@ -2391,20 +2417,12 @@ function answeredBy(repo) {
2391
2417
  }
2392
2418
  }
2393
2419
  // Answer a run waiting at its gate. At a spend gate, approve runs the journey and skip ends the run with nothing spent. At
2394
- // the judge gate only fix answers: it ends the run fail, since nothing moves a run past its judge and no receipt is ever
2395
- // written without a judge pass. The runner has exited, so this command finishes the run; the respond lock lets only one
2396
- // answer (or an abort) close it.
2397
- async function cmdRespond(argv) {
2398
- const { values } = parseArgs({ args: argv, options: { run: { type: 'string' }, action: { type: 'string' }, reason: { type: 'string' } } });
2399
- const action = values.action;
2400
- if (action !== 'approve' && action !== 'skip' && action !== 'fix')
2401
- throw new UsageError('respond needs --action approve, skip or fix', 'Run: done-done respond --run <run-id> --action <approve|skip|fix> [--reason "<why>"]');
2402
- const reason = values.reason?.replace(/\s+/g, ' ').trim() ?? '';
2403
- const repo = repoRoot();
2404
- const cfg = loadConfig(repo);
2405
- const root = runsRoot(cfg.app);
2406
- const id = pickId(root, repo, 'respond', cfg, values.run);
2407
- const dir = join(root, id);
2420
+ // the judge gate only fix and skip answer: fix ends the run fail (the product or the verify skill is fixed next, and the run
2421
+ // goes again), skip ends it fail too, without the fix. Nothing moves a run past its judge, so approve never answers it and
2422
+ // no receipt is ever written without a judge pass. The runner has exited, so this command finishes the run; the respond
2423
+ // lock lets only one answer (or an abort) close it.
2424
+ // A sign-off board answer and a terminal answer both come through here, so they change the run the same way.
2425
+ async function answerGate(dir, id, action, reason, by) {
2408
2426
  const notAtGate = (r) => new UsageError(`run ${id} is not waiting at its gate (it is ${r.status === 'awaiting_approval' ? 'being answered' : shownStatus(r)})`, `Read it: done-done status --run ${id}`);
2409
2427
  if (!createOnce(join(dir, 'respond.lock')))
2410
2428
  throw notAtGate(loadRun(dir));
@@ -2414,12 +2432,11 @@ async function cmdRespond(argv) {
2414
2432
  throw notAtGate(run);
2415
2433
  if (run.gate.spend && action === 'fix')
2416
2434
  throw new UsageError(`run ${id} is at a spend gate, which takes --action approve or skip`, `Run: done-done respond --run ${id} --action approve|skip --reason "<why>"`);
2417
- if (!run.gate.spend && action !== 'fix')
2418
- throw new UsageError(`run ${id} is at its judge gate, which takes only --action fix: done-done never moves a run past its judge, and writes no receipt without a judge pass`, `Run: done-done respond --run ${id} --action fix, then fix the product or the verify skill's recipe, commit, and run again`);
2435
+ if (!run.gate.spend && action === 'approve')
2436
+ throw new UsageError(`run ${id} is at its judge gate, which takes only --action fix or skip: done-done never moves a run past its judge, and writes no receipt without a judge pass`, `Run: done-done respond --run ${id} --action fix, then fix the product or the verify skill's recipe, commit, and run again`);
2419
2437
  if (action !== 'fix' && !reason)
2420
2438
  throw new UsageError(`respond --action ${action} needs --reason "<why>": the spend answer is recorded with who and why`, `Run: done-done respond --run ${id} --action ${action} --reason "<why>"`);
2421
2439
  const gate = run.steps.find((s) => s.name === 'gate');
2422
- const by = answeredBy(repo);
2423
2440
  run.response = { action, by, ...(reason && { reason }), at: now() };
2424
2441
  logEvent(dir, { step: 'gate', response: action, by, ...(reason && { reason }) });
2425
2442
  // The spend gate: approve runs the journey in a new runner; skip ends the run with nothing spent.
@@ -2428,34 +2445,106 @@ async function cmdRespond(argv) {
2428
2445
  if (action !== 'approve') {
2429
2446
  setStep(dir, run, drive, 'failed');
2430
2447
  finishRun(dir, run, 'fail', `gate: ${by} chose ${action}: the journey was not run, so nothing was spent`);
2431
- return showRun(dir, cfg, 'respond');
2448
+ return { gone: false };
2432
2449
  }
2433
2450
  Object.assign(run, { spend: { by, reason, at: run.response.at }, status: 'pending', gate: undefined, response: undefined, runner: undefined });
2434
2451
  setStep(dir, run, drive, 'pending');
2435
2452
  for (const lock of ['runner.lock', 'respond.lock'])
2436
2453
  rmSync(join(dir, lock));
2437
- return launch(dir, cfg, 'respond', {});
2454
+ return await startRunner(dir, {});
2438
2455
  }
2439
2456
  setStep(dir, run, gate, 'failed');
2440
- finishRun(dir, run, 'fail', `gate: ${by} chose fix: ${reason || run.gate.findings[0].what}`);
2441
- return showRun(dir, cfg, 'respond');
2457
+ finishRun(dir, run, 'fail', `gate: ${by} chose ${action}: ${reason || run.gate.findings[0].what}`);
2458
+ return { gone: false };
2442
2459
  }
2443
2460
  catch (e) {
2444
2461
  rmSync(join(dir, 'respond.lock'), { force: true });
2445
2462
  throw e;
2446
2463
  }
2447
2464
  }
2465
+ // Answer a run waiting at its gate, from the terminal.
2466
+ async function cmdRespond(argv) {
2467
+ const { values } = parseArgs({ args: argv, options: { run: { type: 'string' }, action: { type: 'string' }, reason: { type: 'string' } } });
2468
+ const action = values.action;
2469
+ if (action !== 'approve' && action !== 'skip' && action !== 'fix')
2470
+ throw new UsageError('respond needs --action approve, skip or fix', 'Run: done-done respond --run <run-id> --action <approve|skip|fix> [--reason "<why>"]');
2471
+ const reason = values.reason?.replace(/\s+/g, ' ').trim() ?? '';
2472
+ const repo = repoRoot();
2473
+ const cfg = loadConfig(repo);
2474
+ const root = runsRoot(cfg.app);
2475
+ const id = pickId(root, repo, 'respond', cfg, values.run);
2476
+ const dir = join(root, id);
2477
+ const answered = await answerGate(dir, id, action, reason, answeredBy(repo));
2478
+ return showRun(dir, cfg, 'respond', answered.gone);
2479
+ }
2480
+ // The sign-off board's own CLI (github.com/umeranjum17/sign-off): done-done posts through it and never edits a board.
2481
+ // It prints one document: `field: value` lines, a table and a help list.
2482
+ function signoffRun(args, cwd) {
2483
+ const r = spawnSync('sign-off-axi', args, { cwd, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
2484
+ if (r.error?.code === 'ENOENT')
2485
+ throw new UsageError('sign-off-axi is not on PATH, so nothing was posted', 'Install the board CLI: npm install -g sign-off-axi, then run this again');
2486
+ return { code: r.status ?? 1, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
2487
+ }
2488
+ // One field of that document: the value of the first line that starts with `name:`.
2489
+ const signoffField = (out, name) => {
2490
+ const line = out.split('\n').find((l) => l.startsWith(`${name}:`));
2491
+ if (!line)
2492
+ return '';
2493
+ const raw = line.slice(name.length + 1).trim();
2494
+ return raw.startsWith('"') && raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1) : raw;
2495
+ };
2496
+ // Its help line: the value of the first `help[n]:` line.
2497
+ const signoffHelp = (out) => {
2498
+ const line = out.split('\n').find((l) => l.startsWith('help['));
2499
+ if (!line)
2500
+ return '';
2501
+ const raw = line.slice(line.indexOf(':') + 1).trim();
2502
+ return raw.startsWith('"') && raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1) : raw;
2503
+ };
2504
+ // Post the message to the board and read back the card it made.
2505
+ function postToBoard(cwd, board, kind, file, key) {
2506
+ const { code, out } = signoffRun(['post', ...(board ? [board] : []), '--kind', kind, '--from', file], cwd);
2507
+ if (code !== 0)
2508
+ throw new UsageError(signoffField(out, 'error') || `sign-off-axi post ended with exit ${code}`, signoffHelp(out) || 'Read it: sign-off-axi post --help');
2509
+ const shown = out.split('\n').some((l) => { const t = l.trim(); return t === key || t.startsWith(`${key},`) || t.startsWith(`${key} `); });
2510
+ if (!shown)
2511
+ throw new UsageError(`sign-off-axi posted without the card ${key}, so the run has no sign-off card`, 'Read the board: sign-off-axi list');
2512
+ return { board: signoffField(out, 'board') || board, key };
2513
+ }
2514
+ // Wait for the answer to a card on the board and read it back.
2515
+ function waitForAnswer(cwd, board, key, seconds) {
2516
+ const { code, out } = signoffRun(['wait', ...(board ? [board] : []), '--key', key, '--json', '--timeout', String(seconds)], cwd);
2517
+ if (code !== 0)
2518
+ throw new UsageError(signoffField(out, 'error') || `sign-off-axi wait ended with exit ${code}`, signoffHelp(out) || `Wait again: done-done signoff --wait`);
2519
+ let answers;
2520
+ try {
2521
+ answers = JSON.parse(out);
2522
+ }
2523
+ catch {
2524
+ throw new UsageError(`sign-off-axi wait printed no answer for the card ${key}`, `Read it: sign-off-axi answers --key ${key}`);
2525
+ }
2526
+ const answer = answers[key];
2527
+ if (!answer)
2528
+ throw new UsageError(`the board card ${key} has no answer`, `Read it: sign-off-axi answers --key ${key}`);
2529
+ return { value: answer.answer, ...(answer.label && { label: answer.label }), ...(answer.at && { at: answer.at }), ...(answer.note && { note: answer.note }) };
2530
+ }
2448
2531
  // sign-off's communication contract v1 (github.com/umeranjum17/sign-off, docs/contract.md): done-done sends `gate` and
2449
2532
  // `evidence`, schema 1. Each value is checked against the contract's limits first, so a run that cannot make a valid
2450
- // message writes no file. done-done never posts it: sign-off is the user's choice, so the help line names the post command.
2451
- const GATE_LABELS = { approve: 'Approve the spend', skip: 'Skip the spend', fix: 'Fix first' };
2452
- function cmdSignoff(argv) {
2453
- const { values } = parseArgs({ args: argv, options: { run: { type: 'string' } } });
2533
+ // message writes no file. Without the opt-in done-done never posts it: sign-off is the user's choice, so the help line
2534
+ // names the post command. With the opt-in (--post, or signoff.post in .done-done.yaml) done-done posts it through
2535
+ // sign-off-axi, records the card on the run and can wait for the answer to feed back to that run.
2536
+ // The contract fixes a gate's options: approve (Approve), skip (Skip the judge) and fix (Fix first), and it takes two or
2537
+ // three of them. A spend gate answers approve or skip; a judge gate answers fix or skip, never approve: nothing moves a
2538
+ // run past its judge.
2539
+ const GATE_LABELS = { approve: 'Approve', skip: 'Skip the judge', fix: 'Fix first' };
2540
+ async function cmdSignoff(argv) {
2541
+ const { values } = parseArgs({ args: argv, options: { run: { type: 'string' }, post: { type: 'boolean' }, board: { type: 'string' }, wait: { type: 'boolean' }, timeout: { type: 'string' } } });
2454
2542
  const repo = repoRoot();
2455
2543
  const cfg = loadConfig(repo);
2456
2544
  const root = runsRoot(cfg.app);
2457
2545
  const id = pickId(root, repo, 'signoff', cfg, values.run);
2458
- const run = loadRun(join(root, id));
2546
+ const dir = join(root, id);
2547
+ let run = loadRun(dir);
2459
2548
  const refuse = (what, help) => { throw new UsageError(`run ${id} cannot make a valid sign-off message, so no file was written: ${what}`, help); };
2460
2549
  const text = (field, v, max, help = `Read it: done-done status --run ${id}`) => {
2461
2550
  const n = [...v].length;
@@ -2472,6 +2561,44 @@ function cmdSignoff(argv) {
2472
2561
  // names the capture file alone: strip any <evidence root>/<one run folder>/ prefix.
2473
2562
  const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2474
2563
  const bare = (s) => s.replaceAll(new RegExp(`${esc(cfg.evidence.root)}${esc(sep)}[^${esc(sep)}]+${esc(sep)}`, 'g'), '');
2564
+ // The board: the one the user names (--board, or signoff.board), else the nearest .sign-off/ board sign-off-axi finds.
2565
+ const boardName = values.board ?? cfg.signoff.board;
2566
+ const board = boardName ? resolve(repo, tilde(boardName)) : '';
2567
+ // The opt-in: --post posts this run, signoff.post in .done-done.yaml posts every run. Without it nothing is posted.
2568
+ const posts = values.post === true || cfg.signoff.post;
2569
+ // Wait for the answer to the card this run posted and feed it back to that run.
2570
+ const awaitCard = async (card) => {
2571
+ const limit = parseDuration(values.timeout ?? '10m');
2572
+ if (limit === null)
2573
+ throw new UsageError(`--timeout ${values.timeout} is not a duration such as 90s, 10m or 1h`, 'Run: done-done signoff --wait --timeout 10m');
2574
+ const answer = waitForAnswer(repo, board || tilde(card.board), card.key, Math.round(limit / 1000));
2575
+ card.answer = { value: answer.value, ...(answer.label && { label: answer.label }), ...(answer.at && { at: answer.at }), ...(answer.note && { note: answer.note }) };
2576
+ run = loadRun(dir);
2577
+ run.signoff = card;
2578
+ saveRun(dir, run);
2579
+ logEvent(dir, { step: 'signoff', answered: card.key, answer: answer.value });
2580
+ if (card.kind === 'gate') {
2581
+ const action = answer.value;
2582
+ if (action !== 'approve' && action !== 'skip' && action !== 'fix')
2583
+ throw new UsageError(`the board card ${card.key} was answered ${action}, which is not an action of done-done respond`, `Read it: done-done status --run ${id}`);
2584
+ const reason = answer.note?.replace(/\s+/g, ' ').trim() || `answered ${action} on the sign-off board card ${card.key}`;
2585
+ await answerGate(dir, id, action, reason, answeredBy(repo));
2586
+ }
2587
+ run = loadRun(dir);
2588
+ emit({
2589
+ command: 'signoff', run_id: id, kind: card.kind, file: card.file, board: card.board,
2590
+ card: { key: card.key, at: card.at }, answer: card.answer,
2591
+ run: { status: shownStatus(run), ...(run.result && { result: run.result }), ...(run.reason && { reason: run.reason }) },
2592
+ help: [`Read the run: done-done status --run ${id}`],
2593
+ });
2594
+ return runCode(run, shownStatus(run));
2595
+ };
2596
+ // Without the opt-in, --wait only reads the card the run already posted; it never posts one.
2597
+ if (values.wait && !posts) {
2598
+ if (!run.signoff)
2599
+ throw new UsageError(`run ${id} has no sign-off card, so there is no answer to wait for`, `Post it first: done-done signoff --post --run ${id}`);
2600
+ return await awaitCard(run.signoff);
2601
+ }
2475
2602
  let msg, file;
2476
2603
  if (run.kind === 'verify' && run.status === 'awaiting_approval' && run.gate) {
2477
2604
  const { findings, spend, head } = run.gate;
@@ -2480,8 +2607,8 @@ function cmdSignoff(argv) {
2480
2607
  contract: 1, kind: 'gate', schema: 1, key: `dd-gate-${id}`, filer: 'done-done',
2481
2608
  title: text('title', spend ? `Spend ${spend} on the ${cfg.app} ${drove} run?` : `End the ${cfg.app} ${drove} run its judge did not pass, to fix it?`, 160),
2482
2609
  about: text('about', `${bare(findings[0].what)}${more}`, 500), run: runRef(head.commit),
2483
- // A spend gate takes approve or skip; the judge gate only fix, since nothing moves a run past its judge.
2484
- options: (spend ? ['approve', 'skip'] : ['fix']).map((value) => ({ value, label: GATE_LABELS[value] })),
2610
+ // The contract's gate vocabulary: a spend gate takes approve or skip, a judge gate fix or skip, and nothing else.
2611
+ options: (spend ? ['approve', 'skip'] : ['fix', 'skip']).map((value) => ({ value, label: GATE_LABELS[value] })),
2485
2612
  recommend_value: spend ? 'approve' : 'fix', close: 'done',
2486
2613
  };
2487
2614
  file = join(root, id, 'signoff-gate.json');
@@ -2542,7 +2669,23 @@ function cmdSignoff(argv) {
2542
2669
  return refuse(`it is ${shownStatus(run)}${run.kind === 'verify' ? '' : ` (a ${run.kind} run)`}; signoff writes a gate for a verify run waiting at its gate, or evidence for a finished verify run`, `Read it: done-done status --run ${id}`);
2543
2670
  writeFileSync(file, `${JSON.stringify(msg, null, 2)}\n`);
2544
2671
  const at = citable(repo, file);
2545
- emit({ command: 'signoff', run_id: id, kind: msg.kind, file: at, help: [`Post it to the sign-off board (done-done never posts): npx -y sign-off-axi post --kind ${msg.kind} --from ${arg(at)}`] });
2672
+ if (!posts) {
2673
+ emit({ command: 'signoff', run_id: id, kind: msg.kind, file: at, help: [`Post it to the sign-off board (done-done never posts): npx -y sign-off-axi post --kind ${msg.kind} --from ${arg(at)}`] });
2674
+ return PASS;
2675
+ }
2676
+ // Post it through the board's own CLI and record the card it made, so the claim traces to the run and its card.
2677
+ const posted = postToBoard(repo, board, String(msg.kind), at, String(msg.key));
2678
+ const card = { key: posted.key, kind: msg.kind, board: posted.board, file: at, at: now() };
2679
+ run.signoff = card;
2680
+ saveRun(dir, run);
2681
+ logEvent(dir, { step: 'signoff', posted: card.key, kind: card.kind });
2682
+ if (values.wait)
2683
+ return await awaitCard(card);
2684
+ emit({
2685
+ command: 'signoff', run_id: id, kind: card.kind, file: at, board: card.board,
2686
+ card: { key: card.key, at: card.at },
2687
+ help: [`Wait for the answer: done-done signoff --wait --run ${id}`, `Read the card: sign-off-axi answers --key ${card.key}`],
2688
+ });
2546
2689
  return PASS;
2547
2690
  }
2548
2691
  function runsOf(repo, cfg) {
@@ -4050,7 +4193,7 @@ async function main(argv) {
4050
4193
  if (cmd === 'history')
4051
4194
  return cmdHistory(rest);
4052
4195
  if (cmd === 'signoff')
4053
- return cmdSignoff(rest);
4196
+ return await cmdSignoff(rest);
4054
4197
  if (cmd === '__runner') {
4055
4198
  await runSteps(rest[0]);
4056
4199
  return PASS;
package/dist/config.js CHANGED
@@ -76,12 +76,14 @@ const KEYS = {
76
76
  'checks.upstream_catch_up': { scope: 'repo', type: 'boolean' },
77
77
  'checks.moved_from': { scope: 'repo', type: 'boolean' },
78
78
  'checks.generated_paths': { scope: 'repo', type: 'string[]' },
79
+ 'signoff.post': { scope: 'repo', type: 'boolean' },
80
+ 'signoff.board': { scope: 'repo', type: 'string' },
79
81
  'judge.model': { scope: 'global', type: 'string' },
80
82
  'judge.route': { scope: 'global', type: 'route' },
81
83
  'judge.floor': { scope: 'global', type: 'number' },
82
84
  'judge.env_file': { scope: 'global', type: 'string' },
83
85
  };
84
- const SECTIONS = new Set(['evidence', 'secrets', 'checks', 'judge']);
86
+ const SECTIONS = new Set(['evidence', 'secrets', 'checks', 'judge', 'signoff']);
85
87
  const MAPS = new Set(['auto_fix', 'agent_config', 'agent_args_override', 'agent_path_override']);
86
88
  export const home = () => process.env.DONE_DONE_HOME || join(homedir(), '.done-done');
87
89
  // A duration such as 90s, 30m or 1h, in milliseconds; null when the text is not one or is not a delay a timer can run.
@@ -281,6 +283,7 @@ function defaultConfig(repo) {
281
283
  evidence: { local_root: join(home(), 'evidence'), root: '', allow_dirty: false, ignore_paths: [] },
282
284
  secrets: { patterns: {}, allow: [] },
283
285
  checks: { fix_title: '^(fix|bugfix|hotfix)\\b', upstream_catch_up: false, moved_from: false, generated_paths: [] },
286
+ signoff: { post: false, board: '' },
284
287
  judge: { route: '', model: '', floor: 0.7, env_file: '' },
285
288
  trusted: false,
286
289
  from: {},
package/docs/index.html CHANGED
@@ -3,87 +3,116 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <title>done-done: no proof, no "done"</title>
7
- <meta name="description" content="done-done won't accept &quot;done&quot; without real, fresh proof from your product, captured at the exact commit being called done.">
8
- <meta property="og:title" content="done-done">
9
- <meta property="og:description" content="No proof, no &quot;done&quot;.">
6
+ <title>done-done — prove your app really works</title>
7
+ <meta name="description" content="Prove your app really works before you call it done. A command-line check of real screenshots, recordings and output, for developers using coding agents.">
8
+ <meta property="og:title" content="done-done — prove your app really works">
9
+ <meta property="og:description" content="Tests pass. Your agent says done. The app is still broken. Ask for proof from the product.">
10
10
  <link rel="icon" href="logo.svg" type="image/svg+xml">
11
11
  <meta name="theme-color" content="#F5F5F7" media="(prefers-color-scheme: light)">
12
12
  <meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)">
13
13
  <style>
14
- /* The same Spacious tokens as sign-off's page: grey grouped page and white tiles by day, black and #161617 by night. */
15
- :root { color-scheme: light dark; --page: #F5F5F7; --tile: #FFFFFF; --sunk: #E8E8ED; --line: #D2D2D7; --text: #1D1D1F; --muted: #6E6E73; --accent: #0066CC; --action: #0071E3; --code: #1D1D1F; --code-text: #F5F5F7; }
16
- @media (prefers-color-scheme: dark) { :root { --page: #000000; --tile: #161617; --sunk: #2C2C2E; --line: #424245; --text: #F5F5F7; --muted: #86868B; --accent: #2997FF; --code: #161617; --code-text: #F5F5F7; } }
17
- * { box-sizing: border-box; }
18
- html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; }
19
- body { margin: 0; background: var(--page); color: var(--text); overflow-wrap: anywhere; -webkit-font-smoothing: antialiased;
20
- font: 1.0625rem/1.4706 -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI", Roboto, sans-serif; letter-spacing: -0.022em; }
21
- a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; }
22
- :focus-visible { outline: 3px solid var(--action); outline-offset: 2px; border-radius: 0.5rem; }
23
- img, picture { display: block; max-width: 100%; height: auto; }
24
- code { font: 0.9em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0; }
25
- .wrap { max-width: 68rem; margin: 0 auto; padding: 0 max(1.25rem, env(safe-area-inset-left)) 0 max(1.25rem, env(safe-area-inset-right)); }
26
- nav { display: flex; align-items: center; gap: 1rem; padding: 1.25rem 0; }
27
- nav .brand { display: flex; align-items: center; gap: 0.625rem; font-weight: 600; color: var(--text); }
28
- nav .brand img { width: 2rem; height: 2rem; border-radius: 0.5rem; }
29
- nav .links { margin-left: auto; display: flex; gap: 1.25rem; font-size: 0.9375rem; }
30
- nav .links a.gh { display: none; }
31
- h1 { font-size: clamp(2.25rem, 7vw, 4rem); line-height: 1.05; letter-spacing: -0.03em; margin: 0 0 1rem; font-weight: 700; }
32
- h2 { font-size: clamp(1.625rem, 4.5vw, 2.5rem); line-height: 1.1; letter-spacing: -0.028em; margin: 0 0 0.75rem; font-weight: 700; }
33
- h3 { font-size: 1.25rem; margin: 0 0 0.25rem; letter-spacing: -0.02em; }
34
- p { margin: 0 0 1rem; } .lead { font-size: 1.25rem; line-height: 1.4; color: var(--muted); max-width: 38rem; }
35
- section { padding: 3rem 0; }
36
- .hero { padding: 2rem 0 1rem; text-align: center; } .hero .lead { margin: 0 auto 1.5rem; }
37
- .actions { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-bottom: 2.5rem; }
38
- .btn { display: inline-flex; align-items: center; min-height: 2.75rem; padding: 0 1.375rem; border-radius: 999px; background: var(--action); color: #fff; font-weight: 600; }
39
- .btn:hover { text-decoration: none; filter: brightness(1.08); }
40
- .btn.quiet { background: var(--sunk); color: var(--text); }
41
- .shot { border-radius: 1rem; border: 1px solid var(--line); overflow: hidden; background: var(--tile); }
42
- .hero .shot { max-width: 44rem; margin: 0 auto; }
43
- .grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
44
- .tile { background: var(--tile); border-radius: 1.25rem; padding: 1.5rem; }
45
- .step { counter-increment: step; } .step::before { content: counter(step); display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: 50%; background: var(--action); color: #fff; font-weight: 600; font-size: 0.9375rem; margin-bottom: 0.75rem; }
46
- .steps { counter-reset: step; }
47
- pre { margin: 0.5rem 0 0; padding: 0.75rem 1rem; border-radius: 0.75rem; background: var(--code); color: var(--code-text); overflow-x: auto; font: 0.875rem/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0; white-space: pre; }
48
- .tile p.note { margin: 0.75rem 0 0; color: var(--muted); }
49
- .figs { display: grid; gap: 1rem; grid-template-columns: 1fr; }
50
- .figs figure { margin: 0; } .figs figcaption { margin-top: 0.5rem; font-size: 0.9375rem; color: var(--muted); } .figs b { color: var(--text); display: block; }
51
- .figs .shot { background: transparent; }
52
- ul.plain { padding-left: 1.25rem; margin: 0 0 1rem; } ul.plain li { margin-bottom: 0.5rem; }
53
- .sibs { display: grid; gap: 1rem; grid-template-columns: 1fr; }
54
- .tile p.what { margin: 0; color: var(--muted); }
55
- footer { padding: 2.5rem 0 3rem; color: var(--muted); font-size: 0.9375rem; display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; }
56
- @media (min-width: 48rem) {
57
- nav .links a.gh { display: inline; }
58
- .grid.three { grid-template-columns: repeat(3, 1fr); } .figs { grid-template-columns: repeat(2, 1fr); }
59
- .sibs { grid-template-columns: repeat(2, 1fr); } section { padding: 4.5rem 0; }
14
+ :root { color-scheme: light dark; --page:#fafbfe; --tile:#fff; --sunk:#eceef7; --line:#dadde9; --text:#1b2031; --muted:#555e73; --accent:#4846c9; --action:#4846c9; --code:#11131c; --code-text:#eef0fb; }
15
+ @media (prefers-color-scheme:dark) { :root { --page:#0d1017; --tile:#151a25; --sunk:#232a3c; --line:#30384b; --text:#eff1fa; --muted:#a7b0c7; --accent:#b2b0ff; --action:#b2b0ff; } }
16
+ * { box-sizing:border-box; }
17
+ html { -webkit-text-size-adjust:100%; text-size-adjust:100%; }
18
+ body { margin:0; background:var(--page); color:var(--text); font:16px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; -webkit-font-smoothing:antialiased; overflow-wrap:anywhere; }
19
+ a { color:var(--accent); text-underline-offset:3px; } a:hover { text-decoration-thickness:2px; }
20
+ :focus-visible { outline:3px solid var(--accent); outline-offset:5px; }
21
+ .skip { position:absolute; left:1rem; top:-5rem; background:var(--tile); padding:1rem; z-index:2; } .skip:focus { top:1rem; }
22
+ img,picture { display:block; max-width:100%; height:auto; }
23
+ .wrap { max-width:1000px; margin:auto; padding:0 max(20px,env(safe-area-inset-left)) 0 max(20px,env(safe-area-inset-right)); }
24
+ nav { display:flex; align-items:center; gap:1rem; min-height:72px; border-bottom:1px solid var(--line); }
25
+ nav a { text-decoration:none; }
26
+ .brand { display:flex; align-items:center; gap:10px; color:var(--text); font-size:18px; font-weight:650; }
27
+ .brand img { width:28px; height:28px; }
28
+ .links { margin-left:auto; display:flex; gap:24px; font-size:14px; }
29
+ h1,h2,h3 { line-height:1.12; letter-spacing:-.035em; text-wrap:balance; }
30
+ h1 { max-width:960px; font-size:clamp(36px,5.3vw,68px); margin:0 auto 16px; font-weight:650; }
31
+ h2 { max-width:680px; font-size:clamp(26px,3.5vw,36px); margin:0 0 20px; font-weight:650; }
32
+ h3 { font-size:20px; margin:10px 0 12px; }
33
+ p { margin:0 0 16px; max-width:760px; }
34
+ .hero { padding:48px 0 40px; text-align:center; }
35
+ .definition { color:var(--muted); font-size:18px; margin:0 auto 8px; }
36
+ .audience { font-weight:600; font-size:15px; margin:0 auto 4px; }
37
+ .problem { color:var(--muted); font-size:16px; margin:0 auto 20px; }
38
+ .actions { display:flex; justify-content:center; align-items:center; gap:12px; margin:0 0 24px; }
39
+ .btn { display:inline-flex; align-items:center; justify-content:center; min-height:44px; padding:0 20px; background:var(--action); color:var(--page); border:1px solid var(--action); border-radius:9px; font-weight:600; font-size:14px; text-decoration:none; }
40
+ .btn.quiet { background:transparent; color:var(--text); border-color:var(--line); }
41
+ .actions .github { font-size:14px; }
42
+ .start { max-width:720px; margin:auto; }
43
+ figure { margin:0 auto 16px; max-width:560px; }
44
+ .quickstart .eyebrow { margin-bottom:6px; }
45
+ .quickstart pre { max-width:560px; margin:0 auto 12px; text-align:left; }
46
+ .shot { border:1px solid #393b4c; border-radius:12px; overflow:hidden; background:#11131c; }
47
+ figcaption { font-size:12px; color:var(--muted); margin-top:8px; line-height:1.4; }
48
+ .eyebrow { display:block; color:var(--accent); font-size:11px; font-weight:700; letter-spacing:.07em; margin:0 0 10px; }
49
+ code { font: .9em ui-monospace,SFMono-Regular,Consolas,monospace; }
50
+ pre { margin:0 0 16px; padding:16px; background:var(--code); color:var(--code-text); border:1px solid #393b4c; border-radius:10px; overflow:auto; font:13px/1.65 ui-monospace,monospace; }
51
+ pre code { font:inherit; }
52
+ section { padding:48px 0; border-top:1px solid var(--line); }
53
+ .lead { max-width:760px; color:var(--muted); font-size:18px; }
54
+ .grid,.sibs { display:grid; gap:16px; margin:24px 0; }
55
+ .grid.three { grid-template-columns:repeat(3,1fr); }
56
+ .tile { padding:24px; border:1px solid var(--line); border-radius:12px; background:var(--tile); }
57
+ .tile p { margin:0; color:var(--muted); font-size:15px; }
58
+ .sibs { grid-template-columns:repeat(2,1fr); }
59
+ .plain { padding-left:20px; max-width:800px; } .plain li { margin:0 0 12px; }
60
+ .note { color:var(--muted); font-size:14px; }
61
+ footer { border-top:1px solid var(--line); display:flex; flex-wrap:wrap; gap:12px 24px; padding:28px 0; color:var(--muted); font-size:13px; }
62
+ @media (max-width:720px) {
63
+ nav { min-height:60px; } .links { gap:18px; } .links .gh,.links a:last-child { display:none; }
64
+ .hero { padding:24px 0; } h1 { font-size:36px; line-height:1.08; margin-bottom:12px; }
65
+ .definition { font-size:14px; line-height:1.4; } .audience { font-size:13px; }
66
+ .problem { font-size:14px; line-height:1.4; margin-bottom:14px; }
67
+ .actions { gap:10px; margin-bottom:16px; } .btn { font-size:12px; min-height:40px; padding:0 12px; } .actions .github { font-size:12px; }
68
+ figure { max-width:360px; } figcaption { font-size:11px; margin-top:6px; }
69
+ .quickstart { max-width:400px; margin:auto; } .eyebrow { margin-bottom:6px; }
70
+ pre { padding:12px; font-size:12px; margin-bottom:12px; }
71
+ section { padding:32px 0; } .grid.three,.sibs { grid-template-columns:1fr; }
60
72
  }
61
- @media (min-width: 64rem) { .sibs { grid-template-columns: repeat(4, 1fr); } }
62
73
  </style>
63
74
  </head>
64
75
  <body>
76
+ <a class="skip" href="#main">Skip to content</a>
65
77
  <div class="wrap">
66
78
  <nav aria-label="Main">
67
79
  <a class="brand" href="./"><img src="logo.svg" alt="" width="32" height="32">done-done</a>
68
80
  <div class="links"><a href="#install">Install</a><a href="#see">See it work</a><a class="gh" href="https://github.com/umeranjum17/done-done-axi">GitHub</a><a href="https://www.npmjs.com/package/done-done-axi">npm</a></div>
69
81
  </nav>
70
82
 
83
+ <main id="main">
71
84
  <header class="hero">
72
- <h1>No proof, no &ldquo;done&rdquo;.</h1>
73
- <p class="lead">done-done won't accept &ldquo;done&rdquo; without real, fresh proof from your product, captured at the exact commit being called done.</p>
74
- <div class="actions"><a class="btn" href="#install">Install in 3 steps</a><a class="btn quiet" href="#see">See it work</a></div>
75
- <div class="shot"><picture><source media="(prefers-color-scheme: dark)" srcset="media/flow-dark.svg"><img src="media/flow-light.svg" width="710" height="270" alt="How it works: capture your real app, tie every file to the exact commit, then check: pass with exit 0 or fail with exit 1."></picture></div>
85
+ <h1>Prove your app really works.</h1>
86
+ <p class="definition">A command-line check of real screenshots, recordings and output.</p>
87
+ <p class="audience">For developers using coding agents.</p>
88
+ <p class="problem">Tests pass. Your agent says &ldquo;done&rdquo;. The app is still broken.</p>
89
+ <div class="actions"><a class="btn" href="#install">Install in 3 steps</a><a class="btn quiet" href="#see">See it work</a><a class="github" href="https://github.com/umeranjum17/done-done-axi">GitHub ↗</a></div>
90
+ <div class="start">
91
+ <figure id="see">
92
+ <div class="shot"><img src="media/demo.png" width="800" height="460" alt="Real sample app: Release checklist shows Capture the real app checked and Done. The actual claude-haiku-5-5 judge passes that screenshot at sample commit 08d635e46a98."></div>
93
+ <figcaption>Real sample app · live judge · captured commit</figcaption>
94
+ </figure>
95
+ <div id="install" class="quickstart">
96
+ <p class="eyebrow">START HERE · NODE 22.18+ + GIT</p>
97
+ <pre><code>npm install -g done-done-axi
98
+ cd your-app &amp;&amp; done-done init
99
+ done-done doctor</code></pre>
100
+ </div>
101
+ </div>
76
102
  </header>
77
103
 
78
- <section id="install">
79
- <h2>Install in 3 steps</h2>
80
- <p class="lead">From npm, with Node 22.18 or newer and git.</p>
81
- <div class="grid three steps">
82
- <div class="tile step"><h3>Install</h3><pre>npm install -g done-done-axi</pre><p class="note">Puts <code>done-done</code> on your PATH.</p></div>
83
- <div class="tile step"><h3>Set up your repo</h3><pre>cd your-app &amp;&amp; done-done init</pre><p class="note">Writes a minimal <code>.done-done.yaml</code> and asks which coding agent and which judge to use. It never picks either for you.</p></div>
84
- <div class="tile step"><h3>Check the setup</h3><pre>done-done doctor</pre><p class="note">Changes nothing and names each missing piece with the command that fixes it.</p></div>
104
+ <section class="setup">
105
+ <h2>How it works</h2>
106
+ <div class="grid three">
107
+ <div class="tile"><span class="eyebrow">01 / CAPTURE</span><h3>Drive the real product.</h3><p>Your agent follows the recipe and saves screenshots or output, tied to the exact commit.</p></div>
108
+ <div class="tile"><span class="eyebrow">02 / CHECK</span><h3>Show the claim is true.</h3><p>A fresh judge reads each capture. Wrong output fails; uncertainty never becomes a pass.</p></div>
109
+ <div class="tile"><span class="eyebrow">03 / KEEP</span><h3>Save a receipt.</h3><p>A pass records the commit and file hashes. No judge pass, no receipt.</p></div>
85
110
  </div>
86
- <p style="margin-top:1rem;color:var(--muted)">Then prove something: <code>done-done verify --outcome "&lt;what a user can now do&gt;"</code>. Or give your agent the skill with <code>npx skills add umeranjum17/done-done-axi --skill done-done -a &lt;agent&gt; -y</code> and ask &ldquo;is this really done?&rdquo;</p>
111
+ <p><code>init</code> asks which coding agent and which judge to use, then writes your repo&rsquo;s setup and a capture recipe. You choose both agents and models. <code>doctor</code> checks setup, not the product: it names missing pieces and how to fix them.</p>
112
+ <p>Review and commit the recipe and verify skill, then describe what a user should be able to do:</p>
113
+ <pre><code>done-done verify --outcome "&lt;what a user can now do&gt;"</code></pre>
114
+ <p class="note">Bring your own signed-in agent or API key. Captures go to the judge&rsquo;s provider on your plan or key. Need no-sudo installation, non-interactive flags or a manual capture? Read the <a href="https://github.com/umeranjum17/done-done-axi/blob/main/docs/manual-cli.md">manual walkthrough</a>.</p>
115
+ <p>Prefer to ask your agent &ldquo;is this really done?&rdquo; Install the <a href="https://github.com/umeranjum17/done-done-axi#use-it-from-your-agent">agent skill</a>: <code>npx skills add umeranjum17/done-done-axi --skill done-done -a &lt;agent&gt; -y</code>.</p>
87
116
  </section>
88
117
 
89
118
  <section>
@@ -92,15 +121,9 @@
92
121
  <p class="lead" style="margin-top:-0.25rem">done-done runs your capture and ties every screenshot, recording and log it saves to the exact commit. When someone says &ldquo;done&rdquo;, 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.</p>
93
122
  </section>
94
123
 
95
- <section id="see">
96
- <h2>See it work</h2>
97
- <p class="lead">Four moments from a demo notes app. Every picture is real output from the current build; the demo's judge is a stub, so no model runs.</p>
98
- <div class="figs">
99
- <figure><div class="shot"><picture><source media="(prefers-color-scheme: dark)" srcset="media/run-dark.svg"><img src="media/run-light.svg" width="505" height="478" loading="lazy" alt="done-done run sync records one screenshot for the current commit."></picture></div><figcaption><b>Record a run</b>One screenshot, tied to the current commit.</figcaption></figure>
100
- <figure><div class="shot"><picture><source media="(prefers-color-scheme: dark)" srcset="media/pass-dark.svg"><img src="media/pass-light.svg" width="505" height="838" loading="lazy" alt="A Checks block cites the new screenshot, the judge passes it, and done-done check passes."></picture></div><figcaption><b>Fresh proof passes</b>The judge sees the right screen; the check exits 0.</figcaption></figure>
101
- <figure><div class="shot"><picture><source media="(prefers-color-scheme: dark)" srcset="media/reasons-dark.svg"><img src="media/reasons-light.svg" width="505" height="406" loading="lazy" alt="done-done check fails: one screenshot is from an older commit, one file is empty, one is a blank frame."></picture></div><figcaption><b>Weak proof is blocked</b>Older commit, empty file, blank frame: each named.</figcaption></figure>
102
- <figure><div class="shot"><picture><source media="(prefers-color-scheme: dark)" srcset="media/config-dark.svg"><img src="media/config-light.svg" width="505" height="166" loading="lazy" alt="done-done run stops with exit 2 because the config has an unknown key."></picture></div><figcaption><b>Mistakes stop early</b>A config typo exits 2 before anything runs.</figcaption></figure>
103
- </div>
124
+ <section>
125
+ <h2>No proof, no &ldquo;done&rdquo;.</h2>
126
+ <p>Want to see what passes and what stops? The <a href="https://github.com/umeranjum17/done-done-axi#see-it-work">worked demo</a> shows recording, a judged pass, stale or empty evidence, blank frames and a config typo. Its judge is a disclosed stub, not a live model. The first-screen capture above is different: a real browser-app screenshot and its live <code>claude-haiku-5-5</code> verdict, tied to the displayed sample-app commit.</p>
104
127
  </section>
105
128
 
106
129
  <section>
@@ -122,7 +145,7 @@
122
145
 
123
146
  <section>
124
147
  <h2>Alone, and with the other tools</h2>
125
- <p class="lead">done-done works on its own: the CLI and the skill need nothing else. It is one of four tools that each do one job and connect through small written contracts. Neither needs the others.</p>
148
+ <p class="lead">done-done works on its own: neither the CLI nor the skill needs another tool in this family. Each tool does one job and connects through small written contracts.</p>
126
149
  <div class="sibs">
127
150
  <div class="tile"><h3>done-done</h3><p class="what">Verification. This page.</p></div>
128
151
  <div class="tile"><h3><a href="https://www.npmjs.com/package/sign-off-axi">sign-off</a></h3><p class="what">The board where agents ask you questions. <code>done-done signoff</code> writes a run's gate or evidence in its format; it never posts.</p></div>
@@ -131,13 +154,14 @@
131
154
  </div>
132
155
  </section>
133
156
 
134
- <section>
157
+ <section id="firstmate">
135
158
  <h2>Inside Firstmate, or outside</h2>
136
159
  <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>
137
- <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.</p>
160
+ <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>
138
161
  </section>
139
162
 
140
- <footer><span>done-done · Apache-2.0</span><a href="https://github.com/umeranjum17/done-done-axi">GitHub</a><a href="https://www.npmjs.com/package/done-done-axi">npm</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/docs/reference.md">Reference</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/VISION.md">Vision</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/docs/example-cli.md">Worked example</a></footer>
163
+ </main>
164
+ <footer><span>done-done · Apache-2.0 · early preview</span><a href="https://github.com/umeranjum17/done-done-axi">GitHub</a><a href="https://www.npmjs.com/package/done-done-axi">npm</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/docs/reference.md">Reference</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/VISION.md">Vision</a><a href="https://github.com/umeranjum17/done-done-axi/blob/main/docs/example-cli.md">Worked example</a></footer>
141
165
  </div>
142
166
  </body>
143
167
  </html>
@@ -0,0 +1,76 @@
1
+ # Manual CLI
2
+
3
+ [Overview and quick start](../README.md) · [Full reference](reference.md)
4
+
5
+
6
+ **1. Install**
7
+
8
+ ```sh
9
+ npm install -g --prefix ~/.local done-done-axi
10
+ ```
11
+
12
+ `--prefix ~/.local` installs into your home folder, so it needs no `sudo`. This installs two names for the same CLI: `done-done-axi` and the short `done-done` used below. If `command -v done-done` prints nothing, add `~/.local/bin` to your `PATH`: `export PATH="$HOME/.local/bin:$PATH"`.
13
+
14
+ To skip the install, run `npx -y done-done-axi` wherever this README says `done-done`.
15
+
16
+ **2. Set it up in your app's repo**
17
+
18
+ ```sh
19
+ cd your-app
20
+ done-done init
21
+ ```
22
+
23
+ `init` writes a minimal `.done-done.yaml` (only if you don't already have one) that names your app, and a commented global `~/.done-done/config.yaml` (only if there is none). done-done never picks the coding agent it starts: `init` asks you in a terminal, or takes `--agent <agent> --model <model>`. It never picks the judge either, the agent of its own that `check`, `receipt`, `verify` and `design` always start to look at each capture: `init` asks in a terminal, or takes `--judge <agent> --judge-model <model>`. Any agent and model you choose runs, your coding agent's own too (the judge is a fresh session of its own). The config keys are in the [reference](reference.md#config). A plain `init` never overwrites an existing config. When your coding agent is set, `init` also has it write a verify skill, `.agents/skills/done-done-verify-<app>/SKILL.md`, from the repo's own files (never overwriting one; with no agent set, no skill is written; an older `verify-<app>` skill is renamed to this once). Check its launch never touches real accounts or your own state. Evidence lives outside the repo, in `~/.done-done/evidence/<app>`, so it outlives the checkout that captured it. For an `answerer:pi` judge with no explicit model, done-done uses `openai-codex/gpt-6-luna`; `judge.model` or `agent_config.pi.model` overrides it. Then describe how to capture proof — swap the command for whatever drives your real app the way a user does and saves its screenshots, recordings or output (here a CLI's own output):
24
+
25
+ ```yaml
26
+ journeys:
27
+ smoke:
28
+ command: npm start > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log && node record-steps.mjs
29
+ core: true
30
+ ```
31
+
32
+ The command runs from the repo root with `DONE_DONE_EVIDENCE_ROOT` (a new folder for this run in the evidence root) and `DONE_DONE_RUN_ID` set, and `${RUN_ID}` in it is replaced. A run that writes nothing into its folder fails. The command also writes `steps.json` beside its captures: the target version, the state it started from, the steps a user takes and the result a user sees. `check`, `receipt` and `gate` read the claim against that record, so a test log wrapped in a script cannot prove a claim about the product ([the record](docs/reference.md#step-record)). A journey that only runs code checks (`npm test`, `yarn run check`, `tsc`, `pytest`, a linter or a build) is code-only: its log says the code checks pass, not that the product works, so `init --journey` refuses it and `check`, `receipt` and `gate` never count it as proof ([the rule](docs/reference.md#real-product-evidence)).
33
+
34
+ Name your **core journey** with `core: true`: the one thing a user cannot live without, driven end to end on the real target (for a remote terminal app: pair, open a pane, type, see the output). If your app ships something users install and upgrade (an npm CLI, an Android app, a host service), also record an **upgrade journey** with `upgrade: true` that installs your previous release, upgrades over it in place, then drives the core journey: a fresh install alone is not how users get your release. `doctor` fails until both are named ([the rule](reference.md#core-journey-and-release-path)).
35
+
36
+ Optional: `done-done init --hook <claude|codex|opencode|gemini|pi>` adds a session hook or plugin to the repo that shows each session the bare `done-done` state. Each agent's file and behavior are in the [reference](reference.md#set-up-a-repo).
37
+
38
+ Record the recipe in the repo so anyone can re-run the proof, then commit it (and `.claude/settings.json`, `.codex/hooks.json`, `.gemini/settings.json`, `.opencode/plugins/done-done.js` or `.pi/extensions/done-done.js` if you added a hook):
39
+
40
+ ```sh
41
+ done-done init --journey smoke --core -- 'npm start > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log && node record-steps.mjs'
42
+ git add .done-done.yaml
43
+ git commit -m "Add done-done"
44
+ ```
45
+
46
+ A `run -- <command>` whose command matches no recorded journey still runs, but its result hints that the recipe is not in the repo. For a proof you drive by hand or by your agent instead of a journey, `done-done run --open` prints a run id and a folder to capture into, and `done-done run --close <run-id>` seals it ([how](reference.md#run-a-journey)).
47
+
48
+ At any point, `done-done doctor` checks the setup and changes nothing except the `--probe` call record. Each item reports `ok`, `warning` or `missing` with the exact fix command; an agent you have not set yet is `missing`, with the command that saves your choice, and so is a config whose journeys only run code checks, one that names no core journey, or an app that ships an installable artifact with no upgrade journey. `done-done doctor --probe` also asks the agent one question to prove it answers; on a new machine that probe fails until the agent is signed in (for pi, run `pi` and then `/login`). The quick start above never calls the coding agent, so `run` and `check` work before it is signed in; `check` does call the judge, so set one first. The items are listed in the [reference](reference.md#check-the-setup).
49
+
50
+ `done-done maintain` keeps the verify skill true: it checks the skill's links and feature index, checks that every `file:line` it cites exists, and has your coding agent read the source for each feature, read-only, to report drift. It ends `clean`, `drift` with the findings to fix, or `blocked`. It never runs without that reader: an agent that is not set asks, and one that cannot start or cannot read without tools stops it before the run (exit 2). With `auto_fix: { maintain: <n> }` (as in no-mistakes; 0, the default, reports only), your judge first sorts each drift finding (a skill out of date, or a product that may be wrong, which stops and asks you), then your agent fixes the skill folder only, in a done-done worktree, and maintain runs again, up to n times. A clean end is `changed`: one commit on a `done-done/maintain-<run-id>` branch, your branch untouched ([reference](reference.md#keep-the-verify-skill-true)).
51
+
52
+ **3. Run it, then check it**
53
+
54
+ ```sh
55
+ done-done run smoke
56
+ done-done check --outcome "it starts" ~/.done-done/evidence/your-app/*/*.log
57
+ ```
58
+
59
+ Each command prints one machine-readable result. By default it is [TOON](https://github.com/toon-format/spec) (the compact AXI convention). `run` shows `result: recorded`: it captured evidence, and says nothing about whether it proves the claim. `check` starts your judge on each capture, and only a check the judge passes shows `result: pass`; with no judge set it asks for one, and a judge that fails stops with the cause (exit 2), never a result. Add `--json` for JSON, or `--full` to list every evidence file or finding instead of the first few. After later runs, use the `run_id` the result prints in place of the first `*`.
60
+
61
+ Not sure which journeys a change touches? `done-done affected` lists them from the verify skill's feature map and the paths each feature cites (the core journey when it cannot tell, never fewer), and exits 1 until a receipt for HEAD covers them. Bare `done-done`, which the session hooks run, names them too.
62
+
63
+ When a check passes, save a receipt and re-verify it later:
64
+
65
+ ```sh
66
+ done-done receipt --outcome "it starts" ~/.done-done/evidence/your-app/*/*.log
67
+ done-done receipt --verify "$(git rev-parse HEAD)"
68
+ ```
69
+
70
+ No journey yet? `done-done verify --outcome "<what a user can now do>"` has your coding agent drive one feature of the verify skill the way a user does (`--feature <name>` picks it). Your judge then checks the captures. When it says a capture is wrong or unclear, or a capture it cannot read (a video or other file) is not judged, the run waits at its gate (exit 4) until you answer with `done-done respond --action fix`, which ends it `fail` so you fix the product or the recipe and run again: nothing moves a run past its judge, and no receipt is written without a judge pass. Until then, a new `verify` or `rerun` of the same claim in that checkout is refused, so nothing goes around the gate ([details](reference.md#drive-by-agent)).
71
+
72
+ With `auto_fix: { verify: <n> }` (0, the default, reports only), a failed verify is fixed with your own agent: your judge first sorts each wrong screen (a product bug to fix, or a question that waits at the gate for you), then your agent fixes the product in a done-done worktree with your ignored files copied in, and verify runs again there, up to n times. A pass ends `changed`, with the fix on a `done-done/verify-<run-id>` branch and your branch untouched ([details](reference.md#fix-the-product)).
73
+
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
+
76
+ Run `done-done` with no arguments to see the current config and the next step.
@@ -0,0 +1,40 @@
1
+ // Real browser app + a read-only view of a real done-done check.
2
+ // node docs/media/demo.mjs <fresh-scratch-directory> <port>
3
+ // In its app/ repo: run --open --json, drive the app and save app.png to the
4
+ // printed evidence_root, run --close <id>, then check --json --outcome <claim>
5
+ // <evidence_root>/app.png > <scratch>/check.json. Save open output as open.json.
6
+ // /proof renders that captured app beside its actual verdict and commit.
7
+ import { execFileSync } from 'node:child_process';
8
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
+ import { createServer } from 'node:http';
10
+ import { resolve, join } from 'node:path';
11
+
12
+ const scratch = resolve(process.argv[2]);
13
+ const repo = join(scratch, 'app');
14
+ mkdirSync(scratch, { recursive: true });
15
+ mkdirSync(repo); // Refuse to overwrite any existing demo repository.
16
+ const app = `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Release checklist</title>
17
+ <style>*{box-sizing:border-box}body{margin:0;padding:14px 18px;background:#f6f7fb;color:#202638;font:14px/1.4 system-ui,sans-serif}header{display:flex;align-items:center;gap:8px;margin-bottom:10px}header span{display:grid;place-items:center;background:#5754d7;color:white;border-radius:7px;width:26px;height:26px}h1{font-size:19px;letter-spacing:-.03em;margin:0}ul{margin:0 0 8px;padding:0;list-style:none}li{display:flex;align-items:center;gap:9px;padding:12px;background:white;border:1px solid #dddfea;border-radius:9px}li label{flex:1;font-weight:600}input[type=checkbox]{accent-color:#5551d7;width:18px;height:18px;margin:0}li b{font-size:11px;background:#e3f4ea;color:#1b643f;padding:3px 7px;border-radius:5px}summary{color:#525a71;font-size:12px;cursor:pointer}form{display:flex;gap:6px;margin-top:8px}input[type=text]{min-width:0;flex:1;border:1px solid #bcc1d1;border-radius:6px;padding:8px;font:inherit}button{border:0;background:#5754d7;color:white;padding:8px 12px;border-radius:6px;font:inherit}:focus-visible{outline:2px solid #5754d7;outline-offset:2px}</style>
18
+ <header><span aria-hidden="true">✓</span><h1>Release checklist</h1></header><ul id="tasks"></ul><details open><summary>Add a task</summary><form><input type="text" aria-label="Task" placeholder="What needs checking?" required><button>Add</button></form></details>
19
+ <script>const list=document.querySelector('#tasks');const saved=JSON.parse(localStorage.getItem('tasks')||'[]');function draw(){list.replaceChildren();for(const t of saved){const row=document.createElement('li'),box=document.createElement('input'),label=document.createElement('label'),status=document.createElement('b');box.type='checkbox';box.checked=t.done;box.id='task-'+saved.indexOf(t);label.htmlFor=box.id;label.textContent=t.text;status.textContent=t.done?'Done':'To do';box.onchange=()=>{t.done=box.checked;localStorage.setItem('tasks',JSON.stringify(saved));draw()};row.append(box,label,status);list.append(row)}}document.querySelector('form').onsubmit=e=>{e.preventDefault();const input=e.target.querySelector('input');saved.push({text:input.value,done:false});localStorage.setItem('tasks',JSON.stringify(saved));input.value='';document.querySelector('details').open=false;draw()};draw();</script></html>`;
20
+ writeFileSync(join(repo, 'index.html'), app);
21
+ writeFileSync(join(repo, '.done-done.yaml'), 'app: release-checklist\nevidence:\n root: ../evidence\n');
22
+ const env = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1',
23
+ GIT_AUTHOR_NAME: 'Umer', GIT_AUTHOR_EMAIL: 'umer@example.com', GIT_COMMITTER_NAME: 'Umer', GIT_COMMITTER_EMAIL: 'umer@example.com' };
24
+ for (const args of [['init', '-q', '-b', 'main'], ['add', '.'], ['commit', '-qm', 'Add release checklist sample app']])
25
+ execFileSync('git', args, { cwd: repo, env });
26
+ const escape = s => s.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
27
+ createServer((req, res) => {
28
+ try {
29
+ let body = app;
30
+ if (req.url === '/proof') {
31
+ const run = JSON.parse(readFileSync(join(scratch, 'open.json'), 'utf8'));
32
+ const check = JSON.parse(readFileSync(join(scratch, 'check.json'), 'utf8'));
33
+ if (check.result !== 'pass') throw new Error('The real check has not passed');
34
+ const screenshot = readFileSync(join(run.evidence_root, 'app.png')).toString('base64');
35
+ const model = escape(check.judge.model);
36
+ body = `<!doctype html><html lang="en"><meta charset="utf-8"><title>Real app · real judge</title><style>*{box-sizing:border-box}body{margin:0;background:#11131c;color:#eff0fb;font:14px/1.4 ui-monospace,monospace}img{display:block;width:400px;height:140px}section{padding:12px 18px}p{margin:0 0 5px;color:#b6bad0;font-size:11px}b{color:#83e4ae;font-size:17px}code{font:inherit;color:#eff0fb}</style><img src="data:image/png;base64,${screenshot}" alt="Real captured checklist"><section><p>LIVE JUDGE · ${model}</p><b>PASS</b> <code>· commit ${run.commit.slice(0, 12)}</code><p>Captured app + actual check result</p></section></html>`;
37
+ }
38
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); res.end(body);
39
+ } catch (error) { res.writeHead(503, { 'content-type': 'text/plain' }); res.end(error.message); }
40
+ }).listen(Number(process.argv[3]), '127.0.0.1', function () { console.log(`Sample app: http://127.0.0.1:${this.address().port}/`); });
Binary file
package/docs/reference.md CHANGED
@@ -459,7 +459,7 @@ done-done receipt --verify <head> # read-only: check a recorded receipt
459
459
  ```sh
460
460
  done-done verify [<journey>] --outcome "<what a user can now do>" # --no-wait, --wait (the default), or --foreground (for scripts)
461
461
  done-done verify --outcome "<claim>" [--feature <name>] # no journey: the author agent drives a verify skill feature
462
- done-done respond [--run <run-id>] --action fix | --action approve|skip --reason "<why>" # answer a run at its gate (approve and skip: a spend gate only)
462
+ done-done respond [--run <run-id>] --action fix | --action approve|skip --reason "<why>" # answer a run at its gate (approve: a spend gate only)
463
463
  done-done status [--run <run-id>] # the newest run of this checkout by default
464
464
  done-done attach [--run <run-id>] # stream the run's events to stderr until it ends, then show it
465
465
  done-done logs --step <name> [--run <run-id>] [--full] # one step's log, the last 50 lines unless --full
@@ -487,8 +487,9 @@ With no journey named and none configured (or with `--feature`), the drive step
487
487
  When the judge says `fail` or `unsure` for a capture (or an agent drive has a capture the judge did not read, or no judged capture), the run parks at its `gate` step: `status: awaiting_approval`, exit 4, with one `ask-user` finding per problem (`gate-1`, ...). The runner exits; the run waits until someone answers it with `respond`:
488
488
 
489
489
  - `--action fix`: end the run `fail` (exit 1), with no receipt; fix the product or the verify skill's recipe (`done-done maintain` checks the skill against the source), commit, then `rerun`.
490
+ - `--action skip`: end the run `fail` (exit 1) without the fix, at a spend gate or at the judge gate; the reason says which was skipped.
490
491
 
491
- Fix is the only answer at the judge gate: nothing moves a run past its judge, and no receipt is ever written without a judge pass. `--action approve` or `--action skip` there exits 2 and leaves the run parked (they answer only a spend gate, see [Journeys that spend](#journeys-that-spend)). The answer is logged in `events.jsonl` (`step: gate`, the action and who answered (git `user.name`, else the OS user)) and kept in `run.json` as `response`. A run is answered once (a second `respond` exits 2). `abort` cancels a parked run. While a run waits at its gate, `verify` and `rerun` of the same claim (its `--claim-id`, else the branch it started on) in the same checkout exit 2, naming the waiting run and its `respond` and `abort` commands, so a new run never goes around the gate. A run waiting with neither key, recorded by an older build in the same checkout, blocks every `verify` and `rerun` there until it is answered or aborted. A run record that cannot be read or parsed stops `verify`, `rerun`, `status`, `respond`, `abort` and `history` (exit 2), naming the file to fix or remove.
492
+ Fix and skip are the answers at the judge gate: nothing moves a run past its judge, and no receipt is ever written without a judge pass. `--action approve` there exits 2 and leaves the run parked (it answers only a spend gate, see [Journeys that spend](#journeys-that-spend)). The answer is logged in `events.jsonl` (`step: gate`, the action and who answered (git `user.name`, else the OS user)) and kept in `run.json` as `response`. A run is answered once (a second `respond` exits 2). `abort` cancels a parked run. While a run waits at its gate, `verify` and `rerun` of the same claim (its `--claim-id`, else the branch it started on) in the same checkout exit 2, naming the waiting run and its `respond` and `abort` commands, so a new run never goes around the gate. A run waiting with neither key, recorded by an older build in the same checkout, blocks every `verify` and `rerun` there until it is answered or aborted. A run record that cannot be read or parsed stops `verify`, `rerun`, `status`, `respond`, `abort` and `history` (exit 2), naming the file to fix or remove.
492
493
 
493
494
  #### Fix the product
494
495
 
@@ -513,10 +514,17 @@ A journey with `spends: "<what a run spends>"` (for example `one live generation
513
514
 
514
515
  ```sh
515
516
  done-done signoff [--run <run-id>] # writes the message; never posts it
517
+ done-done signoff --post [--board <file>] # posts it to the board and records the card on the run
518
+ done-done signoff --wait [--timeout <duration>] # waits for the answer to that card and feeds it back to the run
516
519
  npx -y sign-off-axi post --kind <gate|evidence> --from <file> # the command signoff prints
517
520
  ```
518
521
 
519
- `signoff` writes one message in the [sign-off contract v1](https://github.com/umeranjum17/sign-off/blob/main/docs/contract.md) shape. A run waiting at its gate gets a `gate` (`<run>/signoff-gate.json`): key `dd-gate-<run-id>`, the first finding as `about` (its evidence folder is stripped, so it names the capture file), `run` with `run_id` and `commit`, options `approve` (Approve the spend) and `skip` (Skip the spend) on a spend gate, or only `fix` (Fix first) on the judge gate, and `recommend_value` `approve` on a spend gate, else `fix`. The board's answer is `done-done respond --action <value>`. A finished run with captures gets an `evidence` (`signoff-evidence.json` in the evidence folder of the run that made the captures, the source run for a rerun that reused its drive, the fix pass for a run `auto_fix.verify` fixed): key `dd-evidence-<run-id>`, the outcome as `about`, up to 12 PNG, JPEG, WebP, MP4 or WebM captures as `media` (paths relative to that folder) and `run` with `run_id`, `commit` and the four Checks lines (`repro` is the `done-done verify` command that made the run, at its commit, and on the fix branch when a fix pass made it). Both have `filer: done-done`. Every value is checked against the contract's limits first; one that breaks them (an outcome over 500 characters, a capture changed since the run) exits 2 and writes no file. done-done works the same without sign-off.
522
+ `signoff` writes one message in the [sign-off contract v1](https://github.com/umeranjum17/sign-off/blob/main/docs/contract.md) shape. A run waiting at its gate gets a `gate` (`<run>/signoff-gate.json`): key `dd-gate-<run-id>`, the first finding as `about` (its evidence folder is stripped, so it names the capture file), `run` with `run_id` and `commit`, options `approve` (Approve) and `skip` (Skip the judge) on a spend gate, or `fix` (Fix first) and `skip` (Skip the judge) on the judge gate, and `recommend_value` `approve` on a spend gate, else `fix`. The board's answer is `done-done respond --action <value>`. A finished run with captures gets an `evidence` (`signoff-evidence.json` in the evidence folder of the run that made the captures, the source run for a rerun that reused its drive, the fix pass for a run `auto_fix.verify` fixed): key `dd-evidence-<run-id>`, the outcome as `about`, up to 12 PNG, JPEG, WebP, MP4 or WebM captures as `media` (paths relative to that folder) and `run` with `run_id`, `commit` and the four Checks lines (`repro` is the `done-done verify` command that made the run, at its commit, and on the fix branch when a fix pass made it). Both have `filer: done-done`. Every value is checked against the contract's limits first; one that breaks them (an outcome over 500 characters, a capture changed since the run) exits 2 and writes no file. done-done works the same without sign-off.
523
+
524
+ **Posting it (the opt-in).** Nothing is posted unless you ask for it: `done-done signoff` only writes the message and prints the post command. `done-done signoff --post` posts that one message; `signoff.post: true` in `.done-done.yaml` posts every run's message. done-done posts through `sign-off-axi` (the board's own CLI, `npm install -g sign-off-axi`); it never writes a board itself, and a missing `sign-off-axi` is a usage error, never a silent skip. `--board <file>` (or `signoff.board` in `.done-done.yaml`) names the board file; without it, sign-off-axi picks the nearest `.sign-off/` board. The card the board made is recorded on the run (`signoff.key`, `signoff.board`, `signoff.file`), so `done-done status --run <id>` shows the card the claim went to.
525
+
526
+ **The answer comes back.** `done-done signoff --wait` waits for the answer to that card (`--timeout`, 10m by default) and feeds it to the same run: a gate's `approve`, `skip` or `fix` does exactly what `done-done respond --action <value>` does, so an approved spend runs the journey and a fix or a skip ends the run fail. The answer is recorded on the run (`signoff.answer`) with the note the person wrote. An evidence card's verdict (Pass, Fail or Needs work) is recorded, not acted on: that run has already finished. Without the opt-in `--wait` only reads a card the run already posted; it never posts one.
527
+
520
528
 
521
529
  ### Keep the verify skill true
522
530
 
@@ -653,6 +661,8 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
653
661
  | `checks.upstream_catch_up` | `false`; `true` lets `lint` subtract a fork's upstream catch-up lines from the size count, see [Lint check](#lint-check) |
654
662
  | `checks.generated_paths` | `[]`; globs `lint` counts as generated beside lockfiles, `vendor/`, `dist/` and `LICENSE`-type files, see [Lint check](#lint-check) |
655
663
  | `checks.moved_from` | `false`; `true` lets `lint` subtract lines a PR moves from another repo of the same owner, see [Lint check](#lint-check) |
664
+ | `signoff.post` | `false`; `true` lets every `signoff` post its message to the sign-off board, see [Send a run to sign-off](#send-a-run-to-sign-off) |
665
+ | `signoff.board` | none; the board file to post to, else the nearest `.sign-off/` board sign-off-axi finds |
656
666
  | `agent` | as in the global file; set here, it wins |
657
667
  | `auto_fix.maintain` | as in the global file (`0`, report only); set here, it wins |
658
668
  | `auto_fix.verify` | as in the global file (`0`, report only); set here, it wins |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "done-done-axi",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Proof that the real product works before a task is called done: commit-bound evidence runs and evidence checks.",
6
6
  "type": "module",
@@ -31,7 +31,8 @@ First run `command -v done-done`. If it prints a path, run `done-done` as shown
31
31
  Results are TOON: read `result` and follow the `help` lines through
32
32
  `init`, `doctor`, `run`, `verify`, `check` and `receipt`. `design` judges captured screens for visual defects,
33
33
  `maintain` keeps the repo's verify skill true, and `signoff` writes a run's gate or evidence as a sign-off message
34
- (it prints the post command and never posts). Proof comes from driving the real product (the app, CLI or site the way a user does,
34
+ (it prints the post command and never posts; with `--post`, or `signoff.post: true` in .done-done.yaml, it posts that message to the
35
+ sign-off board and `--wait` brings the answer back to the run). Proof comes from driving the real product (the app, CLI or site the way a user does,
35
36
  captured as screenshots, a recording or its transcript), never from a test, typecheck, lint or build run: `init --journey` refuses a journey
36
37
  that only wraps one (`npm test`, `yarn run check`, `tsc`, `pytest`), and `check`, `receipt` and `gate` refuse proof from it. Proof counts only
37
38
  from a committed tree: commit the done-done setup (config, capture script) before you capture. `fail` or