done-done-axi 0.4.1 → 0.4.3

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,9 +1,8 @@
1
1
  <div align="center">
2
2
 
3
- <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="docs/media/wordmark-dark.svg">
5
- <img src="docs/media/wordmark-light.svg" alt="done-done" width="300">
6
- </picture>
3
+ <img src="docs/logo.svg" width="72" height="72" alt="done-done logo">
4
+
5
+ # done-done
7
6
 
8
7
  ### No proof, no "done".
9
8
 
@@ -11,7 +10,25 @@ done-done won't accept "done" without real, fresh proof from your product, captu
11
10
 
12
11
  [![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)
13
12
 
14
- <br>
13
+ </div>
14
+
15
+ ## Install in 3 steps
16
+
17
+ You need Node 22.18 or later and git.
18
+
19
+ ```sh
20
+ npm install -g done-done-axi
21
+ cd your-app && done-done init
22
+ done-done doctor
23
+ ```
24
+
25
+ 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`.
26
+ 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.
27
+ 3. **Check the setup.** `doctor` changes nothing and names each missing piece with the exact command that fixes it.
28
+
29
+ Then prove something: `done-done verify --outcome "<what a user can now do>"`. [Manual CLI](#manual-cli) below walks through journeys, `check` and `receipt`.
30
+
31
+ <div align="center">
15
32
 
16
33
  <picture>
17
34
  <source media="(prefers-color-scheme: dark)" srcset="docs/media/flow-dark.svg">
@@ -59,7 +76,7 @@ npx skills add umeranjum17/done-done-axi --skill done-done -a <agent> -y
59
76
 
60
77
  `<agent>` is `claude-code`, `codex`, `opencode`, `pi` or `gemini-cli`. `-a` and `-y` skip the installer's four prompts and keep it from writing skill folders for agents you do not use.
61
78
 
62
- The skill lands in this project (`.claude/skills/done-done/` for Claude Code, `.agents/skills/done-done/` for Codex, opencode, Pi and Gemini CLI) next to a `skills-lock.json`: commit both so teammates and CI get it too. Or add `-g` to install it once for all your projects (for example `~/.claude/skills/` for Claude Code) and leave the project untouched.
79
+ The skill lands in this project (`.claude/skills/done-done/` for Claude Code, `.agents/skills/done-done/` for Codex, opencode, Pi and Gemini CLI) next to a `skills-lock.json`: commit both so your teammates get it too. Or add `-g` to install it once for all your projects (for example `~/.claude/skills/` for Claude Code) and leave the project untouched.
63
80
 
64
81
  That is the entire setup - no npm install needed.
65
82
  The skill tells your agent to use `done-done` if it is on PATH and otherwise run the npm package through `npx`, so the CLI comes along on demand:
@@ -68,7 +85,7 @@ The skill tells your agent to use `done-done` if it is on PATH and otherwise run
68
85
  npx -y done-done-axi --help
69
86
  ```
70
87
 
71
- The skill stays a short stub and sends the agent to `--help` and the bare command for current instructions, so an installed copy cannot go stale against a newer CLI.
88
+ The skill is a short stub and sends the agent to `--help` and the bare command for current instructions, so an installed copy cannot go stale against a newer CLI.
72
89
 
73
90
  Then, in agents that expose skills as slash commands (Claude Code, for example), invoke it directly:
74
91
 
@@ -101,7 +118,7 @@ cd your-app
101
118
  done-done init
102
119
  ```
103
120
 
104
- `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. It writes no verify skill: write `.agents/skills/verify-<app>/SKILL.md` by hand, with a launch that 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 and saves screenshots, recordings or logs:
121
+ `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/verify-<app>/SKILL.md`, from the repo's own files (never overwriting one; with no agent set, no skill is written). 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 and saves screenshots, recordings or logs:
105
122
 
106
123
  ```yaml
107
124
  journeys:
@@ -158,17 +175,29 @@ Run `done-done` with no arguments to see the current config and the next step.
158
175
  - ⏱️ **Fresh**: Proof from an older commit fails. Commits that only touch docs can be set to keep it fresh.
159
176
  - ♻️ **Not reused**: Proof made only of files already used for a different claim fails, and so does citing the same file twice. A claim is named by `--claim-id <id>`, which defaults to your branch, so rewording it keeps its proof. Several claims on one branch each need their own `--claim-id`.
160
177
  - 🧾 **Honest claims**: Reads a pull request's `## Checks` block — from a file, a URL, or stdin — and makes sure every line is filled in.
161
- - 📌 **Verifiable later**: `receipt` records a passing check against the exact commit and the sha256 of every file it cited; `receipt --verify` re-checks it from any checkout of that commit on the same machine (a merge gate or a fresh clone), or in CI once the evidence folder is copied there ([how](docs/reference.md#where-proof-lives)).
178
+ - 📌 **Verifiable later**: `receipt` records a passing check against the exact commit and the sha256 of every file it cited; `receipt --verify` re-checks it from any checkout of that commit on the same machine (a merge gate or a fresh clone) ([how](docs/reference.md#where-proof-lives)).
162
179
  - 👀 **Shows the outcome** (always): `check`, `receipt` and `verify` start done-done's own judge, a fresh, tools-off agent on your own plan or key (Claude Code, opencode, pi or Gemini CLI, or your own OpenAI API key), to say whether each capture is the right screen for the claim (and each text capture, such as a log, JSON or CLI output, the right output), and a wrong one fails. There is no way to turn it off: done-done gives no result without it. The checks above never pass on their own, and a check that cites only files the judge cannot read (a video) stops before the judge, with exit 2. You choose the judge route (`done-done init --judge <agent> --judge-model <model>`, or pick it from the list done-done shows in a terminal; `done-done check --judge <route>` overrides it for one call); for a Pi judge with no explicit model, done-done defaults to `openai-codex/gpt-6-luna` unless `agent_config.pi.model` is set. Any agent and model you choose runs, your coding agent's own too. A judge that fails or times out stops the command with its cause (exit 2; a `verify` run stops with exit 2 and its cause in the run record), and no receipt is written.
163
180
  - 🎨 **Looks right**: `done-done design <screens...> [--mock <screen>=<mock>]` has the same judge look at each captured screen, alone or next to its approved mock, and fails it on stretched or squashed images, clipped or squashed text, overlap, elements off-screen, blank or black strips and broken layout, naming each defect and where it is ([how](docs/reference.md#design-check)).
164
181
  - 📐 **Fits the screen**: `done-done look <url>` opens a web page in headless Chrome at a 390x844 phone viewport, captures it, and fails the journey that calls it on squashed text, clipped text, elements cut off by an edge or a page wider than the screen, naming each element, with no model ([how](docs/reference.md#look-check)).
182
+ - 🧮 **Honest, cheap claim gate**: `done-done lint <PR URL | report file> <evidence folder>` is a no-model gate on a worker's `## Checks` block, on whenever you call it. It prints `PASS` or `FAIL: <reason>` and exits 1 on a fail: the four lines present, proof naming a command or path, a real repro on a fix PR, evidence files that exist, are non-empty, are newer than the evidence folder and differ from each other, at least one never used by another PR (hashes kept in `~/.done-done/lint.hashes`, every run logged to `lint.log` there), and under 1,000 added non-generated lines ([how](docs/reference.md#lint-check)). `evidence: not proven: <cause>` passes.
183
+ - 🚦 **CI gate, no model**: `done-done gate [<head>]` passes when a receipt committed under `.done-done-receipts/` covers the head, with no judge, config, key or evidence files. CI never runs `check`, `run` or `verify` and never gets a model key; proof runs on your desktop ([how](docs/reference.md#ci-gate)).
165
184
  - 🧩 **Page is clean** (with a `dom` block): each captured screen also needs its page-check record. A page that overflows the viewport, logs a console error, has a failed request or shows squashed, clipped or cut-off UI fails, unless an allow list names it ([how](docs/reference.md#dom-assertions)).
166
- - 🔒 **Safe in CI**: `--trusted-ref origin/main` reads the rules from your main branch, so a pull request can't loosen them.
167
185
 
168
186
  Every answer is an exit code: **0** pass, **1** fail, **2** usage or config error (including no agent or judge set, a check that fails before its judge, a capture the judge cannot be sent, or a judge that did not answer: never a result), **3** can't tell either way, **4** a `verify`, `maintain` or `rerun` run is still running, or a `verify` run waits at its gate.
169
187
 
188
+ ## Alone, and with the other tools
189
+
190
+ 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)):
191
+
192
+ - [`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`.
193
+ - [`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.
194
+ - [`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.
195
+
170
196
  ## Use with Firstmate or another agent fleet
171
197
 
198
+ done-done works the same with or without Firstmate: nothing below is needed to use it, and the skill and CLI behave identically inside or outside a Firstmate home.
199
+
200
+
172
201
  [Firstmate](https://github.com/kunchenguid/firstmate) hands tasks to agent workers and merges their work. done-done ships what those workers need to prove each task, and what the merge needs to check that proof, in [`integrations/firstmate/`](integrations/firstmate/).
173
202
 
174
203
  Install the skill once per machine for your workers' agent (the same `-a` values), install the CLI from npm, and copy two files from it into your Firstmate home:
@@ -194,7 +223,7 @@ Another fleet works the same way: add the include text to whatever your workers
194
223
 
195
224
  ## What's next
196
225
 
197
- So far: `init`, `doctor`, `run` (including `--open` and `--close`), `check`, `receipt`, `verify` (by journey or by your coding agent, with a gate you answer with `respond`), `maintain`, the capture judge (always on), the `design` visual check, the `look` step, the run tools (`status`, `attach`, `logs`, `rerun`, `abort`, `signoff`, `history`) and the opt-in session hooks for Claude Code, Codex, opencode, Gemini CLI and pi. Coming next: help writing the proof steps for your app.
226
+ So far: `init`, `doctor`, `run` (including `--open` and `--close`), `check`, `receipt`, `lint` (a no-model gate on a Checks block), `gate` (a no-model CI check of a committed receipt), `verify` (by journey or by your coding agent, with a gate you answer with `respond`), `maintain`, the capture judge (always on), the `design` visual check, the `look` step, the run tools (`status`, `attach`, `logs`, `rerun`, `abort`, `signoff`, `history`) and the opt-in session hooks for Claude Code, Codex, opencode, Gemini CLI and pi. Coming next: help writing the proof steps for your app.
198
227
 
199
228
  ## Develop
200
229
 
@@ -205,7 +234,7 @@ npm install
205
234
  npm install -g --prefix ~/.local . # optional: links this checkout as done-done on your PATH
206
235
  npm test # builds, then runs the end-to-end tests against throwaway git repos
207
236
  npm run build:skill # regenerate skills/done-done/SKILL.md from src/skill.ts
208
- npm run build:skill -- --check # fail if the committed skill is out of date (CI runs this)
237
+ npm run build:skill -- --check # fail if the committed skill is out of date
209
238
  ```
210
239
 
211
240
  To try an unreleased commit without cloning, run it straight from GitHub (npm 12 needs `--allow-git=all`):
@@ -216,4 +245,4 @@ npx -y --allow-git=all github:umeranjum17/done-done-axi --help
216
245
 
217
246
  `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.
218
247
 
219
- 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 wordmark and the flow picture are hand-made SVGs.
248
+ 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.
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ import { cellEnv, cellName, cellSuffix, hasMatrix, matrixManifest, splitMatrix }
18
18
  import { design, KINDS } from './design.js';
19
19
  import { checkMedia } from './media.js';
20
20
  import { domIssues, domRecord, isDomRecord, lookIssues, SQUASHED_TEXT } from './dom.js';
21
+ import { lint } from './lint.js';
21
22
  import { look } from './look.js';
22
23
  import { scanSecrets } from './secrets.js';
23
24
  const PASS = 0, FAIL = 1, USAGE = 2, INCONCLUSIVE = 3, WAITING = 4;
@@ -92,7 +93,7 @@ const FLAGS = {
92
93
  '--close <run-id>': 'seal an open run and record the files in its folder',
93
94
  '--outcome "<claim>"': 'the outcome the evidence files prove; the files follow as arguments',
94
95
  '--from <file | - | PR URL>': 'read the claim from the newest "## Checks" block of a file, stdin or a PR body',
95
- '--trusted-ref <ref>': 'read .done-done.yaml from this git ref instead of the working tree (use in CI)',
96
+ '--trusted-ref <ref>': 'read .done-done.yaml from this git ref instead of the working tree (for scripts)',
96
97
  '--judge <route>': 'check with this judge for this call instead of judge.route (same form: claude-code, answerer:opencode, answerer:pi, answerer:gemini or openai:key); its model is that agent\'s default, or judge.model when the route is the configured one',
97
98
  '--citation-base <dir>': 'resolve evidence citations against this directory (default: cwd); absolute citations stay unchanged; does not change repo, config or run storage',
98
99
  '--claim-id <id>': 'the claim the evidence proves; evidence already used for another claim id is reuse (default: the current branch, or the PR branch with --from <PR URL>; required on a detached HEAD)',
@@ -100,7 +101,7 @@ const FLAGS = {
100
101
  '--outcome "<what a user can now do>"': 'the outcome the run must prove',
101
102
  '--wait': 'wait until the run ends (the default)',
102
103
  '--no-wait': 'start the run and return at once (exit 4); read it later with done-done status',
103
- '--foreground': 'run every step in this process instead of a detached runner (use in CI)',
104
+ '--foreground': 'run every step in this process instead of a detached runner (for scripts)',
104
105
  '--run <run-id>': 'the run to show (default: the newest run of this checkout)',
105
106
  '--probe': 'ask the configured agent one JSON-schema question and require a valid answer (one call at the agent\'s model: its cheap default where it has one, else its own; set agent_config.<agent>.model to choose)',
106
107
  '--mock <screen>=<mock>': 'judge this screen next to its approved mock (repeat per screen; the screen is judged even when not listed)',
@@ -173,7 +174,7 @@ const COMMANDS = {
173
174
  usage: [
174
175
  ['done-done verify [<journey>] --outcome "<what a user can now do>" [--claim-id <id>]', 'drive the journey in a run that outlives this command, check its evidence, judge each capture and record a receipt'],
175
176
  ['done-done verify --outcome "<claim>" [--feature <name>]', 'with no journey, the author agent drives a verify skill feature and the judge the user set judges its captures'],
176
- ['done-done verify [<journey>] --outcome "<claim>" --no-wait | --wait | --foreground', 'return at once, wait until the run ends or waits at its gate (default), or run in this process (CI)'],
177
+ ['done-done verify [<journey>] --outcome "<claim>" --no-wait | --wait | --foreground', 'return at once, wait until the run ends or waits at its gate (default), or run in this process'],
177
178
  ],
178
179
  flags: ['--outcome "<what a user can now do>"', '--feature <name>', '--claim-id <id>', '--wait', '--no-wait', '--foreground'],
179
180
  more: [RUNS_TEXT],
@@ -197,6 +198,26 @@ const COMMANDS = {
197
198
  overflow, squashed text, text clipped by its container and elements cut off by the left, right or top edge, writes the
198
199
  screenshot and its .dom.json record, and exits 1 naming each element, kind and text, 0 when there is none. A hit always
199
200
  fails: never wrap it in || true. Needs Chrome or Chromium (DONE_DONE_CHROME names the binary)`],
201
+ },
202
+ gate: {
203
+ usage: [['done-done gate [<head>] [--dir <folder>] [--pushed <time>]', 'no-model check for a hosted runner: PASS or FAIL: <reason> for a receipt committed under .done-done-receipts/']],
204
+ flags: ['--dir <folder>', '--pushed <time>'],
205
+ more: [`gate: reads git objects only, so it needs no config, judge, model key or evidence files, and a hosted runner never runs check, run or verify.
206
+ Copy the receipt that receipt wrote (<evidence root>/.done-done/receipts/<head>.json) into .done-done-receipts/ and commit it in a follow-up commit.
207
+ PASS when a receipt there is a well-formed judged pass, its head is <head> (default HEAD) or an ancestor of it, every file changed since that head is
208
+ inside the folder, and its time is not after the push (--pushed, default the commit time of <head>). It does not re-hash evidence: that is receipt --verify
209
+ on the machine that holds it. Needs the history back to the receipt head (fetch-depth: 0). Exit 0 PASS, 1 FAIL, 2 usage error`],
210
+ },
211
+ lint: {
212
+ usage: [['done-done lint <PR URL | report file> <evidence folder>', 'merge gate on a worker\'s claim: prints PASS or FAIL: <reason>; the rules need no judge, model or config']],
213
+ flags: [],
214
+ more: [`lint: a deterministic gate that is on whenever you call it. The newest "## Checks" block of the PR body (gh pr view) or report file needs
215
+ outcome:, repro:, proof: and evidence: lines. proof names a command or path, and the repo's verify-<app> skill, or says "no verify skill"
216
+ when the repo (the current folder) has none; repro is not n/a on a fix PR. evidence names files in the evidence folder (or absolute):
217
+ each exists, is non-empty and is newer than the folder itself; no two are byte-identical; at least one has a hash no other PR used
218
+ (hash store lint.hashes in ~/.done-done, or $DONE_DONE_HOME). "evidence: not proven: <cause>" passes. A PR with 1,000 or more added
219
+ non-generated lines fails (lockfiles, vendor/, dist/, LICENSE-type files, deletions and moved lines do not count); a bot PR passes.
220
+ Every run is appended to lint.log next to the store. Exit 0 PASS, 1 FAIL, 2 usage error. Needs gh for a PR URL`],
200
221
  },
201
222
  status: {
202
223
  usage: [['done-done status [--run <run-id>]', "show a run's steps, result, findings and next step"]],
@@ -560,7 +581,7 @@ function claimFromChecks(body, title, repo, cfg, out) {
560
581
  out.push({ level: 'fail', why: 'evidence names no files' });
561
582
  return { outcome: f.outcome, files, title };
562
583
  }
563
- // A PR body also names its head branch, which is the claim a CI checkout (a detached HEAD) is checking.
584
+ // A PR body also names its head branch, which is the claim a detached-HEAD checkout is checking.
564
585
  function readSource(from) {
565
586
  if (from === '-')
566
587
  return { body: readFileSync(0, 'utf8') };
@@ -811,7 +832,7 @@ async function cmdCheck(argv) {
811
832
  }
812
833
  const receiptsDir = (cfg) => join(cfg.evidence.root, '.done-done', 'receipts');
813
834
  // A receipt names a cited file relative to the repo, or to an evidence root outside it under this prefix,
814
- // so any checkout of the repo (or a CI job that restored the evidence root) finds the same file.
835
+ // so any checkout of the repo finds the same file.
815
836
  const IN_EVIDENCE = '<evidence root>/';
816
837
  function receiptPath(repo, cfg, p) {
817
838
  const inRepo = under(repo, p);
@@ -827,6 +848,17 @@ function writeReceipt(cfg, rec) {
827
848
  renameSync(`${path}.tmp`, path);
828
849
  return path;
829
850
  }
851
+ // Why a parsed receipt is not a well-formed passing one (a judge block that names its judge and passes only captures the receipt cites), or null.
852
+ function receiptFault(rec, short) {
853
+ if (!rec || typeof rec !== 'object' || rec.schema !== 2 || rec.result !== 'pass' || !Array.isArray(rec.files) || !rec.files.length)
854
+ return `receipt for ${short} is malformed`;
855
+ if (!rec.files.every((f) => f && typeof f === 'object' && typeof f.path === 'string' && typeof f.sha256 === 'string'))
856
+ return `receipt for ${short} is malformed`;
857
+ const j = rec.judge;
858
+ const badJudge = !j || typeof j !== 'object' || typeof j.route !== 'string' || typeof j.model !== 'string' || !['agent', 'model', 'session-only'].includes(j.independence) || !Array.isArray(j.verdicts) || !j.verdicts.length
859
+ || !j.verdicts.every((v) => v && v.verdict === 'pass' && rec.files.some((f) => f.path === v.path && f.sha256 === v.sha256));
860
+ return badJudge ? `receipt for ${short} has a malformed judge block` : null;
861
+ }
830
862
  // Verify a recorded receipt for `headArg`, which may be a full sha or a ref (HEAD, a branch or a tag).
831
863
  // Read-only, so a missing or changed record is a failure, not a usage error.
832
864
  function verifyReceipt(repo, cfg, headArg) {
@@ -849,16 +881,10 @@ function verifyReceipt(repo, cfg, headArg) {
849
881
  catch (e) {
850
882
  return fail(`receipt for ${short} is malformed (${e.message})`);
851
883
  }
852
- if (!rec || typeof rec !== 'object' || rec.schema !== 2 || rec.result !== 'pass' || !Array.isArray(rec.files) || !rec.files.length)
853
- return fail(`receipt for ${short} is malformed`);
854
- if (!rec.files.every((f) => f && typeof f === 'object' && typeof f.path === 'string' && typeof f.sha256 === 'string'))
855
- return fail(`receipt for ${short} is malformed`);
856
- // The judge block must name its judge, and pass only captures this receipt cites.
884
+ const fault = receiptFault(rec, short);
885
+ if (fault)
886
+ return fail(fault);
857
887
  const j = rec.judge;
858
- const badJudge = !j || typeof j !== 'object' || typeof j.route !== 'string' || typeof j.model !== 'string' || !['agent', 'model', 'session-only'].includes(j.independence) || !Array.isArray(j.verdicts) || !j.verdicts.length
859
- || !j.verdicts.every((v) => v && v.verdict === 'pass' && rec.files.some((f) => f.path === v.path && f.sha256 === v.sha256));
860
- if (badJudge)
861
- return fail(`receipt for ${short} has a malformed judge block`);
862
888
  if (rec.head !== commit)
863
889
  return fail(`receipt records head ${String(rec.head).slice(0, 12)}, not ${short}`);
864
890
  const now = head(repo);
@@ -923,7 +949,7 @@ function recordReceipt(repo, cfg, r) {
923
949
  const rec = { schema: 2, repo: cfg.app, head: r.head.commit, tree: r.head.tree, claim: r.claim ?? '', result: 'pass', at: new Date().toISOString(), files: r.cited, judge: r.judgeBlock };
924
950
  const path = writeReceipt(cfg, rec);
925
951
  const result = { command: 'receipt', result: 'pass', head: r.head.commit, tree: r.head.tree, claim: rec.claim, files: rec.files.length, receipt: shown(repo, path) };
926
- return { code: PASS, result: { ...result, ...(r.judge && { judge: r.judge }), help: [`Verify this receipt (CI or merge): done-done receipt --verify ${r.head.commit}`] } };
952
+ return { code: PASS, result: { ...result, ...(r.judge && { judge: r.judge }), help: [`Verify this receipt (merge gate): done-done receipt --verify ${r.head.commit}`] } };
927
953
  }
928
954
  // Why evidence from `from` no longer proves `to`, or '' if it still does.
929
955
  function staleness(repo, cfg, from, to) {
@@ -949,10 +975,10 @@ function checkHelp(code, findings, files, { cfg, repo, claim, source, citationBa
949
975
  if (judgeLevel && code === INCONCLUSIVE)
950
976
  return ['Look at the capture yourself, or capture a clearer screen of the outcome and re-run this check'];
951
977
  if (code === PASS) {
952
- // The CI gate needs a PR on a remote; without one, a receipt records the pass locally.
978
+ // The merge gate needs a PR on a remote; without one, a receipt records the pass locally.
953
979
  const citationArgs = citationBase === undefined ? '' : ` --citation-base ${arg(citationBase)}`;
954
980
  if (spawnSync('git', ['remote', 'get-url', 'origin'], { cwd: repo }).status === 0)
955
- return [`Gate CI on this proof: done-done check --from <PR URL> --trusted-ref origin/main${citationArgs}`];
981
+ return [`Gate the merge on this proof: done-done check --from <PR URL> --trusted-ref origin/main${citationArgs}`];
956
982
  const claimArgs = source ? ['--from', source] : ['--outcome', claim ?? '', ...files];
957
983
  return [`Record this pass for HEAD: done-done receipt ${claimArgs.map(arg).join(' ')}${citationArgs}`];
958
984
  }
@@ -1880,15 +1906,29 @@ function showRun(dir, cfg, command, gone = false) {
1880
1906
  : /^locate: no verify skill/.test(run.reason ?? '') ? ['Create the verify skill: done-done init']
1881
1907
  : /^locate:/.test(run.reason ?? '') ? [`Make ${cfg.skill} a real folder inside the repository (not a link), commit, then run again: ${again}`]
1882
1908
  : [`Read the step logs in ${shown(run.repo, join(dir, 'logs'))}`, `Fix the cause, then run again: ${again}`])
1883
- : code === PASS ? [`Verify this receipt (CI or merge): done-done receipt --verify ${rec.commit}`]
1909
+ : code === PASS ? [`Verify this receipt (merge gate): done-done receipt --verify ${rec.commit}`]
1884
1910
  : status === 'cancelled' ? [...(run.kind === 'maintain' ? [] : [`Start again from the failed step: done-done rerun --run ${run.run_id}`]), `List past runs: done-done history`]
1885
1911
  : [`Read the step logs in ${shown(run.repo, join(dir, 'logs'))}`, ...(run.kind === 'maintain' ? [] : [`Fix the cause, commit, then run again from the failed step: done-done rerun --run ${run.run_id}`]), `Or start a new run: ${again}`];
1886
1912
  emit(result);
1887
1913
  return code;
1888
1914
  }
1915
+ // A progress line on stderr every 60s (DONE_DONE_HEARTBEAT_MS overrides) naming the running step, so a caller that kills silent commands does not.
1916
+ function heartbeat(dir) {
1917
+ const t = setInterval(() => {
1918
+ const run = loadRun(dir);
1919
+ process.stderr.write(`done-done: run ${run.run_id} still ${run.status}, step ${run.steps.find((s) => s.status === 'running')?.name ?? 'between steps'}\n`);
1920
+ }, Number(process.env.DONE_DONE_HEARTBEAT_MS) || 60_000);
1921
+ return () => clearInterval(t);
1922
+ }
1889
1923
  async function waitRun(dir, pid) {
1890
- while (['pending', 'running'].includes(loadRun(dir).status) && alive(pid))
1891
- await new Promise((r) => setTimeout(r, 200));
1924
+ const stop = heartbeat(dir);
1925
+ try {
1926
+ while (['pending', 'running'].includes(loadRun(dir).status) && alive(pid))
1927
+ await new Promise((r) => setTimeout(r, 200));
1928
+ }
1929
+ finally {
1930
+ stop();
1931
+ }
1892
1932
  }
1893
1933
  // The judge a command will start: the user's agent and judge, asked for when unset.
1894
1934
  async function preflightJudge(cfg, needsAgent = true) {
@@ -1972,7 +2012,13 @@ async function cmdVerify(argv) {
1972
2012
  // runner goes on when this command or its shell ends; --wait (the default) holds this command until the run ends.
1973
2013
  async function launch(dir, cfg, command, how) {
1974
2014
  if (how.foreground) {
1975
- await runSteps(dir);
2015
+ const stop = heartbeat(dir);
2016
+ try {
2017
+ await runSteps(dir);
2018
+ }
2019
+ finally {
2020
+ stop();
2021
+ }
1976
2022
  return showRun(dir, cfg, command);
1977
2023
  }
1978
2024
  const fd = openSync(join(dir, 'logs', 'runner.log'), 'a');
@@ -3446,6 +3492,77 @@ async function cmdLook(argv) {
3446
3492
  });
3447
3493
  return hits.length ? FAIL : PASS;
3448
3494
  }
3495
+ // The folder of a branch where a receipt copied from the evidence root is committed, for `gate`.
3496
+ const GATE_DIR = '.done-done-receipts';
3497
+ // The reason no committed receipt vouches for `tip`, or null when one does. Reads git objects only: no config, no judge, no evidence files.
3498
+ function gateFault(repo, tip, dir, pushed) {
3499
+ const short = tip.slice(0, 12);
3500
+ const names = git(repo, '-c', 'core.quotepath=off', 'ls-tree', '-r', '--name-only', tip, '--', dir).split('\n').filter((n) => n.endsWith('.json'));
3501
+ if (!names.length)
3502
+ return `no receipt in ${dir}/ at ${short}`;
3503
+ const reasons = [];
3504
+ for (const name of names) {
3505
+ const why = (() => {
3506
+ let rec;
3507
+ try {
3508
+ rec = JSON.parse(git(repo, 'show', `${tip}:${name}`));
3509
+ }
3510
+ catch (e) {
3511
+ return `${name} is malformed (${e.message})`;
3512
+ }
3513
+ const fault = receiptFault(rec, basename(name));
3514
+ if (fault)
3515
+ return fault;
3516
+ if (name !== `${dir}/${rec.head}.json`)
3517
+ return `${name} does not name its own head ${String(rec.head).slice(0, 12)}`;
3518
+ try {
3519
+ git(repo, 'merge-base', '--is-ancestor', rec.head, tip);
3520
+ }
3521
+ catch {
3522
+ return `receipt head ${rec.head.slice(0, 12)} is not ${short} or an ancestor of it (a shallow checkout needs full history)`;
3523
+ }
3524
+ const outside = git(repo, '-c', 'core.quotepath=off', 'diff', '--name-only', rec.head, tip).split('\n').filter((n) => n && !n.startsWith(`${dir}/`));
3525
+ if (outside.length)
3526
+ return `${named(outside)} changed after receipt head ${rec.head.slice(0, 12)}, so its proof no longer covers ${short}`;
3527
+ const at = Date.parse(rec.at);
3528
+ if (!(Math.floor(at / 1000) <= Math.floor(pushed / 1000)))
3529
+ return `receipt time ${rec.at} is not before the push (${new Date(pushed).toISOString()})`;
3530
+ return null;
3531
+ })();
3532
+ if (!why)
3533
+ return null;
3534
+ reasons.push(why);
3535
+ }
3536
+ return reasons.join('; ');
3537
+ }
3538
+ // `done-done gate`: the no-model check a hosted runner can run. A receipt committed under GATE_DIR vouches for the head.
3539
+ function cmdGate(argv) {
3540
+ const { values, positionals } = parseArgs({ args: argv, options: { dir: { type: 'string' }, pushed: { type: 'string' } }, allowPositionals: true });
3541
+ if (positionals.length > 1)
3542
+ throw new UsageError('gate takes at most one head', 'Re-run as: done-done gate [<head>] [--dir <folder>] [--pushed <time>]');
3543
+ const repo = repoRoot();
3544
+ let tip;
3545
+ try {
3546
+ tip = git(repo, 'rev-parse', '--verify', `${positionals[0] ?? 'HEAD'}^{commit}`);
3547
+ }
3548
+ catch {
3549
+ throw new UsageError(`gate: ${positionals[0]} is not a commit in this checkout`, 'Pass the pull request head sha, or run it in a checkout of that head');
3550
+ }
3551
+ const pushed = values.pushed === undefined ? Number(git(repo, 'log', '-1', '--format=%ct', tip)) * 1000 : Date.parse(values.pushed);
3552
+ if (Number.isNaN(pushed))
3553
+ throw new UsageError(`gate: --pushed ${values.pushed} is not a time`, 'Pass an ISO time such as 2026-10-20T09:05:56Z');
3554
+ const why = gateFault(repo, tip, (values.dir ?? GATE_DIR).replace(/\/+$/, ''), pushed);
3555
+ console.log(why === null ? 'PASS' : `FAIL: ${why}`);
3556
+ return why === null ? PASS : FAIL;
3557
+ }
3558
+ function cmdLint(argv) {
3559
+ const { positionals } = parseArgs({ args: argv, allowPositionals: true });
3560
+ if (positionals.length !== 2)
3561
+ throw new UsageError('lint needs a PR URL or report file, then the evidence folder', 'Re-run as: done-done lint <PR URL | report file> <evidence folder>');
3562
+ const v = lint(positionals[0], positionals[1], home());
3563
+ console.log(v.ok ? 'PASS' : `FAIL: ${v.why}`);
3564
+ return v.ok ? PASS : FAIL;
3565
+ }
3449
3566
  async function cmdDesign(argv) {
3450
3567
  const { values, positionals } = parseArgs({ args: argv, options: { mock: { type: 'string', multiple: true } }, allowPositionals: true });
3451
3568
  const mocks = new Map();
@@ -3569,6 +3686,10 @@ async function main(argv) {
3569
3686
  return await cmdDesign(rest);
3570
3687
  if (cmd === 'look')
3571
3688
  return await cmdLook(rest);
3689
+ if (cmd === 'lint')
3690
+ return cmdLint(rest);
3691
+ if (cmd === 'gate')
3692
+ return cmdGate(rest);
3572
3693
  if (cmd === 'status')
3573
3694
  return cmdStatus(rest);
3574
3695
  if (cmd === 'attach')
package/dist/lint.js ADDED
@@ -0,0 +1,125 @@
1
+ // `done-done lint`: the deterministic merge gate on a worker's claim. It reads the newest "## Checks" block of a PR body
2
+ // or report file and fails an unproven, reused or oversized claim. No judge, no model, no repo config.
3
+ import { spawnSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs';
6
+ import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
7
+ // Third-party, legal text and committed build output are not reviewable code.
8
+ const GENERATED = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum|[^/]*\.lock)$|(^|\/)vendor\/|(^|\/)dist\/|(^|\/)(LICENSE|LICENSES|NOTICE|COPYING)[^/]*$/;
9
+ export const SIZE_LIMIT = 1000;
10
+ const FIX_TITLE = /^(fix|bugfix|hotfix)\b/i;
11
+ const NOT_APPLICABLE = /^n\/?a\b/i;
12
+ const gh = (...args) => {
13
+ const r = spawnSync('gh', args, { encoding: 'utf8', timeout: 60_000, maxBuffer: 1 << 28 });
14
+ return { status: r.error ? 1 : r.status, stdout: r.stdout ?? '', stderr: r.error?.message ?? r.stderr ?? '' };
15
+ };
16
+ // Added minus deleted non-generated lines of a unified diff: a line moved between files cancels out.
17
+ function netLines(diff) {
18
+ const net = new Map();
19
+ let generated = false;
20
+ for (const l of diff.split('\n')) {
21
+ if (l.startsWith('+++ ')) {
22
+ generated = GENERATED.test(l.startsWith('+++ b/') ? l.slice(6) : l.slice(4));
23
+ continue;
24
+ }
25
+ if (l.startsWith('--- ') || generated || !l.slice(1).trim())
26
+ continue;
27
+ if (l[0] === '+' || l[0] === '-')
28
+ net.set(l.slice(1).trim(), (net.get(l.slice(1).trim()) ?? 0) + (l[0] === '+' ? 1 : -1));
29
+ }
30
+ let total = 0;
31
+ for (const n of net.values())
32
+ if (n > 0)
33
+ total += n;
34
+ return total;
35
+ }
36
+ const sha256 = (p) => createHash('sha256').update(readFileSync(p)).digest('hex');
37
+ // src: a PR URL or a report file; ev: the folder the evidence names are relative to; dir: where the hash store and log live.
38
+ export function lint(src, ev, dir, run = gh) {
39
+ const log = (v) => {
40
+ mkdirSync(dir, { recursive: true });
41
+ appendFileSync(join(dir, 'lint.log'), `${new Date().toISOString()}\t${src}\t${v.head}\t${v.ok ? 'pass' : 'fail'}\t${v.why}\n`);
42
+ return v;
43
+ };
44
+ let body = '', title = '', head = '-', task = '';
45
+ if (existsSync(src) && statSync(src).isFile()) {
46
+ body = readFileSync(src, 'utf8');
47
+ task = basename(dirname(resolve(src)));
48
+ }
49
+ else {
50
+ const r = run('pr', 'view', src, '--json', 'author,body,title,files,headRefOid,headRefName');
51
+ if (r.status)
52
+ return log({ ok: false, why: `gh pr view failed: ${r.stderr.trim().split('\n').pop()?.slice(0, 160) || '?'}`, head });
53
+ const pr = JSON.parse(r.stdout);
54
+ ({ body, title } = { body: pr.body ?? '', title: pr.title ?? '' });
55
+ head = pr.headRefOid ?? '-';
56
+ task = String(pr.headRefName ?? '').split('/').pop();
57
+ const author = pr.author?.login ?? '';
58
+ if (/^(app\/)?(dependabot|renovate)(\[bot\])?$/.test(author))
59
+ return log({ ok: true, why: 'bot', head });
60
+ let size = (pr.files ?? []).reduce((n, f) => n + (GENERATED.test(f.path ?? '') ? 0 : f.additions ?? 0), 0);
61
+ if (size >= SIZE_LIMIT) {
62
+ const d = run('pr', 'diff', src);
63
+ if (!d.status)
64
+ size = netLines(d.stdout);
65
+ }
66
+ if (size >= SIZE_LIMIT)
67
+ return log({ ok: false, why: `${size} added non-generated lines (moved lines excluded): split it (limit ${SIZE_LIMIT})`, head });
68
+ }
69
+ const fail = (why) => log({ ok: false, why, head });
70
+ const block = [...body.matchAll(/^##\s*Checks\s*$([\s\S]*?)(?=^##\s|(?![\s\S]))/gm)].pop(); // newest: workers append, never replace
71
+ if (!block)
72
+ return fail('no "## Checks" block');
73
+ const f = {};
74
+ for (const m of block[1].replace(/```\w*/g, '').matchAll(/^\s*(outcome|repro|proof|evidence)\s*:\s*(.*)$/gm))
75
+ f[m[1]] = m[2].trim();
76
+ const missing = ['outcome', 'repro', 'proof', 'evidence'].filter((k) => !f[k]);
77
+ if (missing.length)
78
+ return fail(`missing or empty Checks line: ${missing.join(', ')}`);
79
+ if (!/[/`]|\.\w{2,4}\b|\b(npm|pnpm|yarn|npx|bash|sh|python3?|node|cargo|go|gradle|make|adb|xcrun|done-done)\b/.test(f.proof))
80
+ return fail('proof: names no command or path');
81
+ const skills = join(process.cwd(), '.agents', 'skills');
82
+ const hasVerify = existsSync(skills) && readdirSync(skills).some((d) => d.startsWith('verify-'));
83
+ if (!f.proof.includes('verify-') && (hasVerify || !/no verify skill/i.test(f.proof)))
84
+ return fail(`proof: does not name the verify-<app> skill run${hasVerify ? '' : ' (or "no verify skill")'}`);
85
+ if (NOT_APPLICABLE.test(f.repro) && FIX_TITLE.test(title))
86
+ return fail('repro: is n/a on a fix PR');
87
+ if (/^not proven\s*:\s*\S/i.test(f.evidence))
88
+ return log({ ok: true, why: `not-proven: ${f.evidence.slice(0, 120)}`, head });
89
+ const names = f.evidence.split(/[,\s]+/).map((n) => n.replace(/^[`"']+|[`"']+$/g, '')).filter((n) => /\.\w{1,10}$/.test(n));
90
+ if (!names.length)
91
+ return fail('evidence: names no files (or write "not proven: <cause>")');
92
+ const store = join(dir, 'lint.hashes');
93
+ const seen = new Map();
94
+ if (existsSync(store))
95
+ for (const l of readFileSync(store, 'utf8').split('\n')) {
96
+ const p = l.split(' ');
97
+ if (p.length === 2)
98
+ seen.set(p[0], p[1]);
99
+ }
100
+ // The evidence folder is born when the task starts, so a file older than it was captured for something else.
101
+ const born = existsSync(ev) ? statSync(ev).birthtimeMs : 0;
102
+ const mine = [];
103
+ let fresh = false;
104
+ for (const n of names) {
105
+ const p = isAbsolute(n) ? n : join(ev, n);
106
+ if (!existsSync(p) || !statSync(p).isFile() || statSync(p).size === 0)
107
+ return fail(`evidence file missing or empty: ${p}`);
108
+ if (born && statSync(p).mtimeMs < born)
109
+ return fail(`evidence file older than the task start: ${p}`);
110
+ const h = sha256(p);
111
+ mine.push(h);
112
+ const owner = seen.get(h); // the same task's earlier lint is not reuse
113
+ if (owner === undefined || owner === src || owner === task)
114
+ fresh = true;
115
+ }
116
+ if (!fresh)
117
+ return fail('every evidence file was already used by another PR');
118
+ if (new Set(mine).size < mine.length)
119
+ return fail('two evidence files are byte-identical: capture each claimed state separately, or write "not proven: <cause>"');
120
+ mkdirSync(dir, { recursive: true });
121
+ for (const h of mine)
122
+ if (!seen.has(h))
123
+ appendFileSync(store, `${h} ${task || src}\n`);
124
+ return log({ ok: true, why: `${names.length} evidence files`, head });
125
+ }
package/dist/look.js CHANGED
@@ -7,7 +7,7 @@ import { domRecord, LOOK, SQUASHED_TEXT } from './dom.js';
7
7
  import { findBin } from './agents.js';
8
8
  // done-done look: opens one page in headless Chrome at a phone viewport over the DevTools protocol (Node's own
9
9
  // WebSocket, no browser library), writes the screenshot and its page-check record next to it, and returns the record.
10
- // Chrome runs without its sandbox so it starts in containers and CI: point look at your own app, not untrusted sites.
10
+ // Chrome runs without its sandbox so it starts in containers: point look at your own app, not untrusted sites.
11
11
  const BROWSERS = ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'];
12
12
  const WAIT = 30_000;
13
13
  export async function look(url, capture, width, height) {
package/docs/logo.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 24 24" role="img" aria-label="done-done logo"><style>.t{fill:#4E5BC9}.c{fill:none;stroke:#FFFFFF;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}@media (prefers-color-scheme:dark){.t{fill:#6C78E0}}</style><rect class="t" width="24" height="24" rx="6"/><path class="c" d="M3.9 12.6l2.6 2.6 4.1-5"/><path class="c" d="M10.4 13.2l2.4 2.4 6.7-8"/></svg>
@@ -119,7 +119,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
119
119
  <g class="l4"><text xml:space="preserve" x="22" y="168" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text></g>
120
120
  <g class="l5"><text xml:space="preserve" x="22" y="192" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
121
121
  <g class="l6"><text xml:space="preserve" x="22" y="216" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
122
- <g class="l7"><text xml:space="preserve" x="22" y="240" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
122
+ <g class="l7"><text xml:space="preserve" x="22" y="240" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
123
123
  <g class="l8"><text xml:space="preserve" x="22" y="264" textLength="57.6" lengthAdjust="spacing">judge:</text></g>
124
124
  <g class="l9"><text xml:space="preserve" x="22" y="288" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text></g>
125
125
  <g class="l10"><text xml:space="preserve" x="22" y="312" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text></g>
@@ -127,7 +127,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
127
127
  <g class="l12"><text xml:space="preserve" x="22" y="360" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text></g>
128
128
  <g class="l13"><text xml:space="preserve" x="22" y="384" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text></g>
129
129
  <g class="l14"><text xml:space="preserve" x="22" y="408" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text></g>
130
- <g class="l15"><text xml:space="preserve" x="22" y="432" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text></g>
130
+ <g class="l15"><text xml:space="preserve" x="22" y="432" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text></g>
131
131
  <g class="l16"><text xml:space="preserve" x="22" y="456" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text></g>
132
132
  <g class="l17"><text xml:space="preserve" x="22" y="480" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text></g>
133
133
  <g class="l18"><text xml:space="preserve" x="22" y="504" textLength="144.0" lengthAdjust="spacing"> note.,false</text></g>
@@ -141,10 +141,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
141
141
  <g class="l26"><text xml:space="preserve" x="22" y="696" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
142
142
  <g class="l27"><text xml:space="preserve" x="22" y="720" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text></g>
143
143
  <g class="l28"><text xml:space="preserve" x="22" y="744" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text></g>
144
- <g class="l29"><text xml:space="preserve" x="22" y="768" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text></g>
145
- <g class="l30"><text xml:space="preserve" x="22" y="792" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text></g>
146
- <g class="l31"><text xml:space="preserve" x="22" y="816" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text></g>
147
- <g class="l32"><text xml:space="preserve" x="22" y="840" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550)"</text></g>
144
+ <g class="l29"><text xml:space="preserve" x="22" y="768" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text></g>
145
+ <g class="l30"><text xml:space="preserve" x="22" y="792" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text></g>
146
+ <g class="l31"><text xml:space="preserve" x="22" y="816" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text></g>
147
+ <g class="l32"><text xml:space="preserve" x="22" y="840" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2)"</text></g>
148
148
  <g class="l33"><text xml:space="preserve" x="22" y="864" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text></g>
149
149
  <g class="l34"><text xml:space="preserve" x="22" y="888" textLength="403.2" lengthAdjust="spacing"> done-done run sync, then check the new</text></g>
150
150
  <g class="l35"><text xml:space="preserve" x="22" y="912" textLength="96.0" lengthAdjust="spacing"> files"</text></g>
@@ -152,18 +152,18 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
152
152
  <g class="l37"><text xml:space="preserve" x="22" y="72" textLength="268.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done run sync</tspan></text><g class="k37"><rect x="118" y="55" width="192.8" height="24" fill="#161b22"/><rect class="h37" x="118" y="56" width="9.6" height="20" fill="#e6edf3" opacity=".7"/></g></g>
153
153
  <g class="l38"><text xml:space="preserve" x="22" y="96" textLength="115.2" lengthAdjust="spacing"><tspan class="d">command: </tspan>run</text></g>
154
154
  <g class="l39"><text xml:space="preserve" x="22" y="120" textLength="153.6" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">recorded</tspan></text></g>
155
- <g class="l40"><text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261018T081521Z-396dc99b-969550</text></g>
156
- <g class="l41"><text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>396dc99b6db71f41bac00a43ac75439e6fb1e91c</text></g>
155
+ <g class="l40"><text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261020T065410Z-fedec802-12efe2</text></g>
156
+ <g class="l41"><text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>fedec802045ebc823249c819dcfc195647ebad75</text></g>
157
157
  <g class="l42"><text xml:space="preserve" x="22" y="192" textLength="115.2" lengthAdjust="spacing"><tspan class="d">dirty: </tspan>false</text></g>
158
158
  <g class="l43"><text xml:space="preserve" x="22" y="216" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
159
159
  <g class="l44"><text xml:space="preserve" x="22" y="240" textLength="451.2" lengthAdjust="spacing"><tspan class="d">record: </tspan>~/.done-done/evidence/notes/.done-done/</text></g>
160
- <g class="l45"><text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261018T081521Z-396dc99b-969550.json</text></g>
160
+ <g class="l45"><text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261020T065410Z-fedec802-12efe2.json</text></g>
161
161
  <g class="l46"><text xml:space="preserve" x="22" y="288" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
162
- <g class="l47"><text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
162
+ <g class="l47"><text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
163
163
  <g class="l48"><text xml:space="preserve" x="22" y="336" textLength="412.8" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Check this proof: done-done check</text></g>
164
164
  <g class="l49"><text xml:space="preserve" x="22" y="360" textLength="364.8" lengthAdjust="spacing"> --outcome \"&lt;what you proved&gt;\" ~/</text></g>
165
165
  <g class="l50"><text xml:space="preserve" x="22" y="384" textLength="288.0" lengthAdjust="spacing"> .done-done/evidence/notes/</text></g>
166
- <g class="l51"><text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png"</text></g>
166
+ <g class="l51"><text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png"</text></g>
167
167
  <g class="l52"><text xml:space="preserve" x="22" y="432" textLength="451.2" lengthAdjust="spacing"> synced a note on two devices, saved after.png</text></g>
168
168
  <g class="l53"><text xml:space="preserve" x="22" y="456" textLength="412.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done check --from pr-body.md</tspan></text><g class="k53"><rect x="118" y="439" width="336.8" height="24" fill="#161b22"/><rect class="h53" x="118" y="440" width="9.6" height="20" fill="#e6edf3" opacity=".7"/></g></g>
169
169
  <g class="l54"><text xml:space="preserve" x="22" y="480" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
@@ -172,7 +172,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
172
172
  <g class="l57"><text xml:space="preserve" x="22" y="552" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text></g>
173
173
  <g class="l58"><text xml:space="preserve" x="22" y="576" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
174
174
  <g class="l59"><text xml:space="preserve" x="22" y="600" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
175
- <g class="l60"><text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
175
+ <g class="l60"><text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
176
176
  <g class="l61"><text xml:space="preserve" x="22" y="648" textLength="57.6" lengthAdjust="spacing">judge:</text></g>
177
177
  <g class="l62"><text xml:space="preserve" x="22" y="672" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text></g>
178
178
  <g class="l63"><text xml:space="preserve" x="22" y="696" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text></g>
@@ -180,7 +180,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
180
180
  <g class="l65"><text xml:space="preserve" x="22" y="744" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text></g>
181
181
  <g class="l66"><text xml:space="preserve" x="22" y="768" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text></g>
182
182
  <g class="l67"><text xml:space="preserve" x="22" y="792" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text></g>
183
- <g class="l68"><text xml:space="preserve" x="22" y="816" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text></g>
183
+ <g class="l68"><text xml:space="preserve" x="22" y="816" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text></g>
184
184
  <g class="l69"><text xml:space="preserve" x="22" y="840" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text></g>
185
185
  <g class="l70"><text xml:space="preserve" x="22" y="864" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text></g>
186
186
  <g class="l71"><text xml:space="preserve" x="22" y="888" textLength="144.0" lengthAdjust="spacing"> note.,false</text></g>
@@ -195,10 +195,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
195
195
  <g class="l80"><text xml:space="preserve" x="22" y="120" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
196
196
  <g class="l81"><text xml:space="preserve" x="22" y="144" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text></g>
197
197
  <g class="l82"><text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text></g>
198
- <g class="l83"><text xml:space="preserve" x="22" y="192" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text></g>
199
- <g class="l84"><text xml:space="preserve" x="22" y="216" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text></g>
200
- <g class="l85"><text xml:space="preserve" x="22" y="240" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text></g>
201
- <g class="l86"><text xml:space="preserve" x="22" y="264" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550)"</text></g>
198
+ <g class="l83"><text xml:space="preserve" x="22" y="192" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text></g>
199
+ <g class="l84"><text xml:space="preserve" x="22" y="216" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text></g>
200
+ <g class="l85"><text xml:space="preserve" x="22" y="240" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text></g>
201
+ <g class="l86"><text xml:space="preserve" x="22" y="264" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2)"</text></g>
202
202
  <g class="l87"><text xml:space="preserve" x="22" y="288" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text></g>
203
203
  <g class="l88"><text xml:space="preserve" x="22" y="312" textLength="403.2" lengthAdjust="spacing"> done-done run sync, then check the new</text></g>
204
204
  <g class="l89"><text xml:space="preserve" x="22" y="336" textLength="96.0" lengthAdjust="spacing"> files"</text></g>
@@ -119,7 +119,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
119
119
  <g class="l4"><text xml:space="preserve" x="22" y="168" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text></g>
120
120
  <g class="l5"><text xml:space="preserve" x="22" y="192" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
121
121
  <g class="l6"><text xml:space="preserve" x="22" y="216" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
122
- <g class="l7"><text xml:space="preserve" x="22" y="240" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
122
+ <g class="l7"><text xml:space="preserve" x="22" y="240" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
123
123
  <g class="l8"><text xml:space="preserve" x="22" y="264" textLength="57.6" lengthAdjust="spacing">judge:</text></g>
124
124
  <g class="l9"><text xml:space="preserve" x="22" y="288" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text></g>
125
125
  <g class="l10"><text xml:space="preserve" x="22" y="312" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text></g>
@@ -127,7 +127,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
127
127
  <g class="l12"><text xml:space="preserve" x="22" y="360" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text></g>
128
128
  <g class="l13"><text xml:space="preserve" x="22" y="384" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text></g>
129
129
  <g class="l14"><text xml:space="preserve" x="22" y="408" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text></g>
130
- <g class="l15"><text xml:space="preserve" x="22" y="432" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text></g>
130
+ <g class="l15"><text xml:space="preserve" x="22" y="432" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text></g>
131
131
  <g class="l16"><text xml:space="preserve" x="22" y="456" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text></g>
132
132
  <g class="l17"><text xml:space="preserve" x="22" y="480" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text></g>
133
133
  <g class="l18"><text xml:space="preserve" x="22" y="504" textLength="144.0" lengthAdjust="spacing"> note.,false</text></g>
@@ -141,10 +141,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
141
141
  <g class="l26"><text xml:space="preserve" x="22" y="696" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
142
142
  <g class="l27"><text xml:space="preserve" x="22" y="720" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text></g>
143
143
  <g class="l28"><text xml:space="preserve" x="22" y="744" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text></g>
144
- <g class="l29"><text xml:space="preserve" x="22" y="768" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text></g>
145
- <g class="l30"><text xml:space="preserve" x="22" y="792" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text></g>
146
- <g class="l31"><text xml:space="preserve" x="22" y="816" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text></g>
147
- <g class="l32"><text xml:space="preserve" x="22" y="840" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550)"</text></g>
144
+ <g class="l29"><text xml:space="preserve" x="22" y="768" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text></g>
145
+ <g class="l30"><text xml:space="preserve" x="22" y="792" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text></g>
146
+ <g class="l31"><text xml:space="preserve" x="22" y="816" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text></g>
147
+ <g class="l32"><text xml:space="preserve" x="22" y="840" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2)"</text></g>
148
148
  <g class="l33"><text xml:space="preserve" x="22" y="864" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text></g>
149
149
  <g class="l34"><text xml:space="preserve" x="22" y="888" textLength="403.2" lengthAdjust="spacing"> done-done run sync, then check the new</text></g>
150
150
  <g class="l35"><text xml:space="preserve" x="22" y="912" textLength="96.0" lengthAdjust="spacing"> files"</text></g>
@@ -152,18 +152,18 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
152
152
  <g class="l37"><text xml:space="preserve" x="22" y="72" textLength="268.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done run sync</tspan></text><g class="k37"><rect x="118" y="55" width="192.8" height="24" fill="#f6f8fa"/><rect class="h37" x="118" y="56" width="9.6" height="20" fill="#1f2328" opacity=".7"/></g></g>
153
153
  <g class="l38"><text xml:space="preserve" x="22" y="96" textLength="115.2" lengthAdjust="spacing"><tspan class="d">command: </tspan>run</text></g>
154
154
  <g class="l39"><text xml:space="preserve" x="22" y="120" textLength="153.6" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">recorded</tspan></text></g>
155
- <g class="l40"><text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261018T081521Z-396dc99b-969550</text></g>
156
- <g class="l41"><text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>396dc99b6db71f41bac00a43ac75439e6fb1e91c</text></g>
155
+ <g class="l40"><text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261020T065410Z-fedec802-12efe2</text></g>
156
+ <g class="l41"><text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>fedec802045ebc823249c819dcfc195647ebad75</text></g>
157
157
  <g class="l42"><text xml:space="preserve" x="22" y="192" textLength="115.2" lengthAdjust="spacing"><tspan class="d">dirty: </tspan>false</text></g>
158
158
  <g class="l43"><text xml:space="preserve" x="22" y="216" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
159
159
  <g class="l44"><text xml:space="preserve" x="22" y="240" textLength="451.2" lengthAdjust="spacing"><tspan class="d">record: </tspan>~/.done-done/evidence/notes/.done-done/</text></g>
160
- <g class="l45"><text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261018T081521Z-396dc99b-969550.json</text></g>
160
+ <g class="l45"><text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261020T065410Z-fedec802-12efe2.json</text></g>
161
161
  <g class="l46"><text xml:space="preserve" x="22" y="288" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
162
- <g class="l47"><text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
162
+ <g class="l47"><text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
163
163
  <g class="l48"><text xml:space="preserve" x="22" y="336" textLength="412.8" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Check this proof: done-done check</text></g>
164
164
  <g class="l49"><text xml:space="preserve" x="22" y="360" textLength="364.8" lengthAdjust="spacing"> --outcome \"&lt;what you proved&gt;\" ~/</text></g>
165
165
  <g class="l50"><text xml:space="preserve" x="22" y="384" textLength="288.0" lengthAdjust="spacing"> .done-done/evidence/notes/</text></g>
166
- <g class="l51"><text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png"</text></g>
166
+ <g class="l51"><text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png"</text></g>
167
167
  <g class="l52"><text xml:space="preserve" x="22" y="432" textLength="451.2" lengthAdjust="spacing"> synced a note on two devices, saved after.png</text></g>
168
168
  <g class="l53"><text xml:space="preserve" x="22" y="456" textLength="412.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done check --from pr-body.md</tspan></text><g class="k53"><rect x="118" y="439" width="336.8" height="24" fill="#f6f8fa"/><rect class="h53" x="118" y="440" width="9.6" height="20" fill="#1f2328" opacity=".7"/></g></g>
169
169
  <g class="l54"><text xml:space="preserve" x="22" y="480" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
@@ -172,7 +172,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
172
172
  <g class="l57"><text xml:space="preserve" x="22" y="552" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text></g>
173
173
  <g class="l58"><text xml:space="preserve" x="22" y="576" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text></g>
174
174
  <g class="l59"><text xml:space="preserve" x="22" y="600" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text></g>
175
- <g class="l60"><text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text></g>
175
+ <g class="l60"><text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text></g>
176
176
  <g class="l61"><text xml:space="preserve" x="22" y="648" textLength="57.6" lengthAdjust="spacing">judge:</text></g>
177
177
  <g class="l62"><text xml:space="preserve" x="22" y="672" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text></g>
178
178
  <g class="l63"><text xml:space="preserve" x="22" y="696" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text></g>
@@ -180,7 +180,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
180
180
  <g class="l65"><text xml:space="preserve" x="22" y="744" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text></g>
181
181
  <g class="l66"><text xml:space="preserve" x="22" y="768" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text></g>
182
182
  <g class="l67"><text xml:space="preserve" x="22" y="792" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text></g>
183
- <g class="l68"><text xml:space="preserve" x="22" y="816" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text></g>
183
+ <g class="l68"><text xml:space="preserve" x="22" y="816" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text></g>
184
184
  <g class="l69"><text xml:space="preserve" x="22" y="840" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text></g>
185
185
  <g class="l70"><text xml:space="preserve" x="22" y="864" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text></g>
186
186
  <g class="l71"><text xml:space="preserve" x="22" y="888" textLength="144.0" lengthAdjust="spacing"> note.,false</text></g>
@@ -195,10 +195,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
195
195
  <g class="l80"><text xml:space="preserve" x="22" y="120" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text></g>
196
196
  <g class="l81"><text xml:space="preserve" x="22" y="144" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text></g>
197
197
  <g class="l82"><text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text></g>
198
- <g class="l83"><text xml:space="preserve" x="22" y="192" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text></g>
199
- <g class="l84"><text xml:space="preserve" x="22" y="216" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text></g>
200
- <g class="l85"><text xml:space="preserve" x="22" y="240" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text></g>
201
- <g class="l86"><text xml:space="preserve" x="22" y="264" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550)"</text></g>
198
+ <g class="l83"><text xml:space="preserve" x="22" y="192" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text></g>
199
+ <g class="l84"><text xml:space="preserve" x="22" y="216" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text></g>
200
+ <g class="l85"><text xml:space="preserve" x="22" y="240" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text></g>
201
+ <g class="l86"><text xml:space="preserve" x="22" y="264" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2)"</text></g>
202
202
  <g class="l87"><text xml:space="preserve" x="22" y="288" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text></g>
203
203
  <g class="l88"><text xml:space="preserve" x="22" y="312" textLength="403.2" lengthAdjust="spacing"> done-done run sync, then check the new</text></g>
204
204
  <g class="l89"><text xml:space="preserve" x="22" y="336" textLength="96.0" lengthAdjust="spacing"> files"</text></g>
@@ -16,7 +16,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
16
16
  <text xml:space="preserve" x="22" y="144" textLength="172.8" lengthAdjust="spacing"><tspan class="d">repro: </tspan>n/a: no bug</text>
17
17
  <text xml:space="preserve" x="22" y="168" textLength="412.8" lengthAdjust="spacing"><tspan class="d">proof: </tspan>no verify skill - done-done run sync</text>
18
18
  <text xml:space="preserve" x="22" y="192" textLength="364.8" lengthAdjust="spacing"><tspan class="d">evidence: </tspan>~/.done-done/evidence/notes/</text>
19
- <text xml:space="preserve" x="22" y="216" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
19
+ <text xml:space="preserve" x="22" y="216" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
20
20
  <text xml:space="preserve" x="22" y="240" textLength="412.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done check --from pr-body.md</tspan></text>
21
21
  <text xml:space="preserve" x="22" y="264" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="115.2" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">pass</tspan></text>
@@ -24,7 +24,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
24
24
  <text xml:space="preserve" x="22" y="336" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text>
25
25
  <text xml:space="preserve" x="22" y="360" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text>
26
26
  <text xml:space="preserve" x="22" y="384" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text>
27
- <text xml:space="preserve" x="22" y="408" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
27
+ <text xml:space="preserve" x="22" y="408" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
28
28
  <text xml:space="preserve" x="22" y="432" textLength="57.6" lengthAdjust="spacing">judge:</text>
29
29
  <text xml:space="preserve" x="22" y="456" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text>
30
30
  <text xml:space="preserve" x="22" y="480" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text>
@@ -32,7 +32,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
32
32
  <text xml:space="preserve" x="22" y="528" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text>
33
33
  <text xml:space="preserve" x="22" y="552" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text>
34
34
  <text xml:space="preserve" x="22" y="576" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text>
35
- <text xml:space="preserve" x="22" y="600" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text>
35
+ <text xml:space="preserve" x="22" y="600" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text>
36
36
  <text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text>
37
37
  <text xml:space="preserve" x="22" y="648" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text>
38
38
  <text xml:space="preserve" x="22" y="672" textLength="144.0" lengthAdjust="spacing"> note.,false</text>
@@ -16,7 +16,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
16
16
  <text xml:space="preserve" x="22" y="144" textLength="172.8" lengthAdjust="spacing"><tspan class="d">repro: </tspan>n/a: no bug</text>
17
17
  <text xml:space="preserve" x="22" y="168" textLength="412.8" lengthAdjust="spacing"><tspan class="d">proof: </tspan>no verify skill - done-done run sync</text>
18
18
  <text xml:space="preserve" x="22" y="192" textLength="364.8" lengthAdjust="spacing"><tspan class="d">evidence: </tspan>~/.done-done/evidence/notes/</text>
19
- <text xml:space="preserve" x="22" y="216" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
19
+ <text xml:space="preserve" x="22" y="216" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
20
20
  <text xml:space="preserve" x="22" y="240" textLength="412.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done check --from pr-body.md</tspan></text>
21
21
  <text xml:space="preserve" x="22" y="264" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="115.2" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">pass</tspan></text>
@@ -24,7 +24,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
24
24
  <text xml:space="preserve" x="22" y="336" textLength="172.8" lengthAdjust="spacing"><tspan class="d">source: </tspan>pr-body.md</text>
25
25
  <text xml:space="preserve" x="22" y="360" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text>
26
26
  <text xml:space="preserve" x="22" y="384" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text>
27
- <text xml:space="preserve" x="22" y="408" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
27
+ <text xml:space="preserve" x="22" y="408" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
28
28
  <text xml:space="preserve" x="22" y="432" textLength="57.6" lengthAdjust="spacing">judge:</text>
29
29
  <text xml:space="preserve" x="22" y="456" textLength="249.6" lengthAdjust="spacing"> route: "answerer:gemini"</text>
30
30
  <text xml:space="preserve" x="22" y="480" textLength="182.4" lengthAdjust="spacing"> model: demo-judge</text>
@@ -32,7 +32,7 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
32
32
  <text xml:space="preserve" x="22" y="528" textLength="211.2" lengthAdjust="spacing"> author: no agent set</text>
33
33
  <text xml:space="preserve" x="22" y="552" textLength="432.0" lengthAdjust="spacing"> judged[1]{path,verdict,reason,seen,cached}:</text>
34
34
  <text xml:space="preserve" x="22" y="576" textLength="307.2" lengthAdjust="spacing"> ~/.done-done/evidence/notes/</text>
35
- <text xml:space="preserve" x="22" y="600" textLength="355.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/</text>
35
+ <text xml:space="preserve" x="22" y="600" textLength="355.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/</text>
36
36
  <text xml:space="preserve" x="22" y="624" textLength="441.6" lengthAdjust="spacing"> after.png,pass,The screen shows the synced</text>
37
37
  <text xml:space="preserve" x="22" y="648" textLength="355.2" lengthAdjust="spacing"> note.,The screen shows the synced</text>
38
38
  <text xml:space="preserve" x="22" y="672" textLength="144.0" lengthAdjust="spacing"> note.,false</text>
@@ -14,10 +14,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
14
14
  <text xml:space="preserve" x="22" y="96" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text>
15
15
  <text xml:space="preserve" x="22" y="120" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text>
16
16
  <text xml:space="preserve" x="22" y="144" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text>
17
- <text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text>
18
- <text xml:space="preserve" x="22" y="192" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text>
19
- <text xml:space="preserve" x="22" y="216" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text>
20
- <text xml:space="preserve" x="22" y="240" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550);</text>
17
+ <text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text>
18
+ <text xml:space="preserve" x="22" y="192" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text>
19
+ <text xml:space="preserve" x="22" y="216" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text>
20
+ <text xml:space="preserve" x="22" y="240" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2);</text>
21
21
  <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> empty.png: empty (0 bytes); blank.png: png</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="451.2" lengthAdjust="spacing"> is a blank frame (luminance spread 0.0000)"</text>
23
23
  <text xml:space="preserve" x="22" y="312" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text>
@@ -14,10 +14,10 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
14
14
  <text xml:space="preserve" x="22" y="96" textLength="134.4" lengthAdjust="spacing"><tspan class="d">command: </tspan>check</text>
15
15
  <text xml:space="preserve" x="22" y="120" textLength="460.8" lengthAdjust="spacing"><tspan class="f b">error: </tspan>"check: the judge was not asked, so there</text>
16
16
  <text xml:space="preserve" x="22" y="144" textLength="451.2" lengthAdjust="spacing"> is no verdict: ~/.done-done/evidence/notes/</text>
17
- <text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png:</text>
18
- <text xml:space="preserve" x="22" y="192" textLength="460.8" lengthAdjust="spacing"> evidence is for commit 396dc99b6db7, HEAD is</text>
19
- <text xml:space="preserve" x="22" y="216" textLength="422.4" lengthAdjust="spacing"> a890428671ea (changed: src/sync.js) (run</text>
20
- <text xml:space="preserve" x="22" y="240" textLength="364.8" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550);</text>
17
+ <text xml:space="preserve" x="22" y="168" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png:</text>
18
+ <text xml:space="preserve" x="22" y="192" textLength="460.8" lengthAdjust="spacing"> evidence is for commit fedec802045e, HEAD is</text>
19
+ <text xml:space="preserve" x="22" y="216" textLength="422.4" lengthAdjust="spacing"> cce55aa4e026 (changed: src/sync.js) (run</text>
20
+ <text xml:space="preserve" x="22" y="240" textLength="364.8" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2);</text>
21
21
  <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> empty.png: empty (0 bytes); blank.png: png</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="451.2" lengthAdjust="spacing"> is a blank frame (luminance spread 0.0000)"</text>
23
23
  <text xml:space="preserve" x="22" y="312" textLength="432.0" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Commit your change, re-capture with</text>
@@ -13,18 +13,18 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
13
13
  <g clip-path="url(#body)"><text xml:space="preserve" x="22" y="72" textLength="268.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done run sync</tspan></text>
14
14
  <text xml:space="preserve" x="22" y="96" textLength="115.2" lengthAdjust="spacing"><tspan class="d">command: </tspan>run</text>
15
15
  <text xml:space="preserve" x="22" y="120" textLength="153.6" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">recorded</tspan></text>
16
- <text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261018T081521Z-396dc99b-969550</text>
17
- <text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>396dc99b6db71f41bac00a43ac75439e6fb1e91c</text>
16
+ <text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261020T065410Z-fedec802-12efe2</text>
17
+ <text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>fedec802045ebc823249c819dcfc195647ebad75</text>
18
18
  <text xml:space="preserve" x="22" y="192" textLength="115.2" lengthAdjust="spacing"><tspan class="d">dirty: </tspan>false</text>
19
19
  <text xml:space="preserve" x="22" y="216" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text>
20
20
  <text xml:space="preserve" x="22" y="240" textLength="451.2" lengthAdjust="spacing"><tspan class="d">record: </tspan>~/.done-done/evidence/notes/.done-done/</text>
21
- <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261018T081521Z-396dc99b-969550.json</text>
21
+ <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261020T065410Z-fedec802-12efe2.json</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text>
23
- <text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
23
+ <text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
24
24
  <text xml:space="preserve" x="22" y="336" textLength="412.8" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Check this proof: done-done check</text>
25
25
  <text xml:space="preserve" x="22" y="360" textLength="364.8" lengthAdjust="spacing"> --outcome \"&lt;what you proved&gt;\" ~/</text>
26
26
  <text xml:space="preserve" x="22" y="384" textLength="288.0" lengthAdjust="spacing"> .done-done/evidence/notes/</text>
27
- <text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png"</text>
27
+ <text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png"</text>
28
28
  <text xml:space="preserve" x="22" y="432" textLength="451.2" lengthAdjust="spacing"> synced a note on two devices, saved after.png</text>
29
29
  <text xml:space="preserve" x="22" y="456" textLength="57.6" lengthAdjust="spacing"><tspan class="d">exit 0</tspan></text></g>
30
30
  </svg>
@@ -13,18 +13,18 @@ text{font:16px ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation
13
13
  <g clip-path="url(#body)"><text xml:space="preserve" x="22" y="72" textLength="268.8" lengthAdjust="spacing"><tspan class="c">~/notes $ </tspan><tspan class="b">done-done run sync</tspan></text>
14
14
  <text xml:space="preserve" x="22" y="96" textLength="115.2" lengthAdjust="spacing"><tspan class="d">command: </tspan>run</text>
15
15
  <text xml:space="preserve" x="22" y="120" textLength="153.6" lengthAdjust="spacing"><tspan class="d">result: </tspan><tspan class="p b">recorded</tspan></text>
16
- <text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261018T081521Z-396dc99b-969550</text>
17
- <text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>396dc99b6db71f41bac00a43ac75439e6fb1e91c</text>
16
+ <text xml:space="preserve" x="22" y="144" textLength="384.0" lengthAdjust="spacing"><tspan class="d">run_id: </tspan>20261020T065410Z-fedec802-12efe2</text>
17
+ <text xml:space="preserve" x="22" y="168" textLength="460.8" lengthAdjust="spacing"><tspan class="d">commit: </tspan>fedec802045ebc823249c819dcfc195647ebad75</text>
18
18
  <text xml:space="preserve" x="22" y="192" textLength="115.2" lengthAdjust="spacing"><tspan class="d">dirty: </tspan>false</text>
19
19
  <text xml:space="preserve" x="22" y="216" textLength="76.8" lengthAdjust="spacing"><tspan class="d">files: </tspan>1</text>
20
20
  <text xml:space="preserve" x="22" y="240" textLength="451.2" lengthAdjust="spacing"><tspan class="d">record: </tspan>~/.done-done/evidence/notes/.done-done/</text>
21
- <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261018T081521Z-396dc99b-969550.json</text>
21
+ <text xml:space="preserve" x="22" y="264" textLength="441.6" lengthAdjust="spacing"> runs/20261020T065410Z-fedec802-12efe2.json</text>
22
22
  <text xml:space="preserve" x="22" y="288" textLength="393.6" lengthAdjust="spacing"><tspan class="d">evidence[1]: </tspan>~/.done-done/evidence/notes/</text>
23
- <text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png</text>
23
+ <text xml:space="preserve" x="22" y="312" textLength="441.6" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png</text>
24
24
  <text xml:space="preserve" x="22" y="336" textLength="412.8" lengthAdjust="spacing"><tspan class="d">help[1]: </tspan>"Check this proof: done-done check</text>
25
25
  <text xml:space="preserve" x="22" y="360" textLength="364.8" lengthAdjust="spacing"> --outcome \"&lt;what you proved&gt;\" ~/</text>
26
26
  <text xml:space="preserve" x="22" y="384" textLength="288.0" lengthAdjust="spacing"> .done-done/evidence/notes/</text>
27
- <text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261018T081521Z-396dc99b-969550/after.png"</text>
27
+ <text xml:space="preserve" x="22" y="408" textLength="451.2" lengthAdjust="spacing"> 20261020T065410Z-fedec802-12efe2/after.png"</text>
28
28
  <text xml:space="preserve" x="22" y="432" textLength="451.2" lengthAdjust="spacing"> synced a note on two devices, saved after.png</text>
29
29
  <text xml:space="preserve" x="22" y="456" textLength="57.6" lengthAdjust="spacing"><tspan class="d">exit 0</tspan></text></g>
30
30
  </svg>
package/docs/reference.md CHANGED
@@ -212,7 +212,7 @@ journeys:
212
212
 
213
213
  Something entirely outside its container or the viewport is hidden on purpose (a closed drawer, a visually hidden label) and is not a hit; a cut element's children are not listed again. Content in a scrolling container (`overflow: auto` or `scroll`, such as a carousel) scrolls into view and is not clipped.
214
214
  - **What it writes.** `--out <file.png>` (default `look.png` in `$DONE_DONE_EVIDENCE_ROOT` inside a journey, else in the current folder) gets the viewport screenshot, exactly the viewport size in pixels, and `<file>.dom.json` next to it the page-check record of [DOM assertions](#dom-assertions), with the page's console errors and failed requests too. look itself fails only on the four kinds above; with a `dom` block, verify's check also reads the record and applies its allow lists to console errors and failed requests.
215
- - **Chrome.** look starts its own headless Chrome or Chromium (`google-chrome`, `google-chrome-stable`, `chromium` or `chromium-browser` on PATH, or `DONE_DONE_CHROME` naming the binary) with a throwaway profile, and removes both when it ends. It runs Chrome without its sandbox so it starts in containers and CI, so point it at your own app, not untrusted sites. No Chrome is a usage error (exit 2) that says how to install or point to one; a page that does not load (its network error, or no load event within 30 seconds) is exit 2 with the cause.
215
+ - **Chrome.** look starts its own headless Chrome or Chromium (`google-chrome`, `google-chrome-stable`, `chromium` or `chromium-browser` on PATH, or `DONE_DONE_CHROME` naming the binary) with a throwaway profile, and removes both when it ends. It runs Chrome without its sandbox so it starts in containers, so point it at your own app, not untrusted sites. No Chrome is a usage error (exit 2) that says how to install or point to one; a page that does not load (its network error, or no load event within 30 seconds) is exit 2 with the cause.
216
216
  - **Limits.** Only the main frame is measured, not an embedded frame; a transform or `clip-path` that hides part of a box is not seen; nothing is clicked or scrolled first, so drive the page to the state to check (a URL per state) before you call look. Look at the capture too: a defect the DOM cannot see (a stretched image, a colour) is the [design check](#design-check)'s.
217
217
 
218
218
  | Result | Exit |
@@ -221,6 +221,17 @@ journeys:
221
221
  | a hit of any kind | 1 |
222
222
  | bad flags, no Chrome, or the page did not load | 2 |
223
223
 
224
+ ### Lint check
225
+
226
+ `done-done lint <PR URL | report file> <evidence folder>` is a deterministic gate on a worker's claim: no judge, no model, no config. It prints `PASS` or `FAIL: <reason>` and exits 0, 1, or 2 on a usage error. A PR URL is read with `gh pr view`; a file is read as the report.
227
+
228
+ - The newest `## Checks` block needs `outcome:`, `repro:`, `proof:` and `evidence:`.
229
+ - `proof:` names a command or path, and the repo's `verify-<app>` skill; it may say `no verify skill` only when `.agents/skills/` in the current folder holds none.
230
+ - `repro:` is not `n/a` when the PR title starts with `fix`, `bugfix` or `hotfix`.
231
+ - `evidence: not proven: <cause>` passes.
232
+ - Otherwise each named file (relative to the evidence folder, or absolute) exists, is non-empty and is newer than the evidence folder itself; no two are byte-identical; and at least one has a hash no other PR used. Hashes are kept in `lint.hashes` and each run is appended to `lint.log`, both in `~/.done-done` (`$DONE_DONE_HOME`). A later lint of the same PR is not reuse.
233
+ - A PR with 1,000 or more added non-generated lines fails. Lockfiles, `vendor/`, `dist/` and `LICENSE`-type files do not count, and neither do deletions or lines moved between files. A dependabot or renovate PR passes.
234
+
224
235
  ### Base-ref before
225
236
 
226
237
  A before/after change is judged as a pair. With `base_ref` set to a branch, tag or commit (for example `base_ref: origin/main`), `verify` drives the base ref too, after the head's drive and only when it passed:
@@ -240,7 +251,7 @@ done-done check --from pr-body.md # a file, `-` for stdin, or a
240
251
 
241
252
  `check` looks only at the files the claim names. It never scans the whole folder.
242
253
 
243
- `check` and `receipt` accept `--citation-base <dir>` for evidence paths supplied as arguments or in a Checks block, for example `done-done check --citation-base /path/to/captures --from pr-body.md`. Relative citations resolve against that directory; a relative base itself resolves against cwd. Absolute citations are unchanged. With the flag, the existing result `evidence` list contains resolved absolute paths. Without it, a relative citation resolves against the repo root first (so a check from a subdirectory finds the same evidence), then against the caller's cwd (so a shell-relative path typed for that shell still works). The flag does not relocate `--from`, change the caller repository/HEAD or config (`--trusted-ref` still selects trusted config), or change `evidence.root`: that remains the run-record, receipt and reuse-ledger store. All provenance, freshness and judge rules still apply. Passing checks preserve an explicit `--citation-base` in their suggested receipt or remote CI gate command; run the follow-up from the same cwd when the base is relative.
254
+ `check` and `receipt` accept `--citation-base <dir>` for evidence paths supplied as arguments or in a Checks block, for example `done-done check --citation-base /path/to/captures --from pr-body.md`. Relative citations resolve against that directory; a relative base itself resolves against cwd. Absolute citations are unchanged. With the flag, the existing result `evidence` list contains resolved absolute paths. Without it, a relative citation resolves against the repo root first (so a check from a subdirectory finds the same evidence), then against the caller's cwd (so a shell-relative path typed for that shell still works). The flag does not relocate `--from`, change the caller repository/HEAD or config (`--trusted-ref` still selects trusted config), or change `evidence.root`: that remains the run-record, receipt and reuse-ledger store. All provenance, freshness and judge rules still apply. Passing checks preserve an explicit `--citation-base` in their suggested receipt or merge gate command; run the follow-up from the same cwd when the base is relative.
244
255
 
245
256
  With `--from`, it reads the newest `## Checks` block:
246
257
 
@@ -343,7 +354,7 @@ done-done design shots/home.png --mock shots/home.png=design/home.png
343
354
  - **Measured strips.** A blank strip is also measured from the pixels of each PNG or JPEG screen: a band along a whole edge, at least 2% of that side deep, in one plain colour with nothing in it, and clearly apart from the screen's main colour. The measurement goes to the judge with the screen, as `measured_from_pixels` with its edge, colour and depth (`left edge: a plain #151815 band 132 px deep (12% of the width), full height, with nothing in it`); the verdict is the judge's, and the measurement never fails a screen on its own. A nav rail or bar that holds icons or a title is content, not a strip, and a screen that is all one colour is the blank-frame check's. A band its `--mock` has too (same edge, about as deep as a share of that side, and in about the same colour) is approved design and is not sent; a band the mock lacks is. If the mock cannot be measured, a band on the screen goes to the judge as one the mock could not be measured to compare. A fully transparent pixel is never measured as part of a band, so a transparent margin is not a strip on a light or dark screen. The measure misses a strip close to the screen's main colour (a black bar on a near-black screen); the judge's question still covers it.
344
355
  - **Mocks.** `--mock <screen>=<mock>` sends the screen and its approved mock in the same request, and the judge reports a difference from the mock as a defect of its kind (a moved, missing or resized element is `layout`); live data such as names, numbers and times is not a defect, and an element shaped as in the mock is not stretched. A dialog, sheet, menu, popover or toast is not a defect only where the mock draws that same overlay in the same place; one the mock lacks, or draws elsewhere, is judged like any other element. Repeat `--mock` per screen; a screen named only in `--mock` is still checked. Without a mock, the screen is judged alone.
345
356
  - **Variants.** `design` judges the screens it is given and captures nothing. Capture each variant your change touches (dark theme, small width, large font) as its own screen and pass them all; a name such as `home.dark.png` or `home.large-font.png` keeps them apart, and each variant takes its own `--mock`. Pass the raw capture: the judge reads everything in the image as the app, so a device frame or letterbox band fails as a `blank-strip`.
346
- - **From a verify flow.** Run it on the screens a journey writes, after the capture, so a failing screen fails the journey and the `verify` run with it: `journeys: { shots: { command: "node capture.mjs && done-done design $DONE_DONE_EVIDENCE_ROOT/*.png" } }`. Or run it as its own CI step on the captured screens.
357
+ - **From a verify flow.** Run it on the screens a journey writes, after the capture, so a failing screen fails the journey and the `verify` run with it: `journeys: { shots: { command: "node capture.mjs && done-done design $DONE_DONE_EVIDENCE_ROOT/*.png" } }`.
347
358
 
348
359
  | Verdict | Result | Exit |
349
360
  |---|---|---|
@@ -356,7 +367,7 @@ The result lists each screen (failing first, then unsure, then passing; `--full`
356
367
 
357
368
  ### Record a receipt
358
369
 
359
- A receipt ties one passing check to one commit and to the exact bytes it cited, so a merge step or CI can re-verify the same proof later.
370
+ A receipt ties one passing check to one commit and to the exact bytes it cited, so a merge gate (or `gate` in CI) can re-verify the same proof later.
360
371
 
361
372
  ```sh
362
373
  done-done receipt --from pr-body.md # or --outcome "<claim>" <files...>, or --from -
@@ -370,7 +381,7 @@ done-done receipt --verify <head> # read-only: check a recorded receipt
370
381
  ### Verify a change in one run
371
382
 
372
383
  ```sh
373
- done-done verify [<journey>] --outcome "<what a user can now do>" # --no-wait, --wait (the default), or --foreground (CI)
384
+ done-done verify [<journey>] --outcome "<what a user can now do>" # --no-wait, --wait (the default), or --foreground (for scripts)
374
385
  done-done verify --outcome "<claim>" [--feature <name>] # no journey: the author agent drives a verify skill feature
375
386
  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)
376
387
  done-done status [--run <run-id>] # the newest run of this checkout by default
@@ -434,7 +445,7 @@ npx -y sign-off-axi post --kind <gate|evidence> --from <file> # the command si
434
445
  ### Keep the verify skill true
435
446
 
436
447
  ```sh
437
- done-done maintain # --no-wait, --wait (the default), or --foreground (CI)
448
+ done-done maintain # --no-wait, --wait (the default), or --foreground (for scripts)
438
449
  ```
439
450
 
440
451
  `maintain` checks the repo's verify skill against the source in a run of kind `maintain`, in the same store as `verify`, so `status`, `attach`, `logs`, `abort` and `history` work on it. Its pass reads only: it never edits the skill, the product code or git, and it records HEAD, `git status --porcelain --untracked-files=all` and the content of each changed or untracked path (regular files are hashed with sha256 as they are read; symlinks are compared by target text and never read through) before and after; if they differ, the run is `blocked` with a `repo-changed` finding. Its steps:
@@ -471,7 +482,7 @@ Running `done-done` with no subcommand prints the repo's config state (`config:
471
482
 
472
483
  `doctor` exits 2 when a required item (`git`, `config`, `evidence_root`, `agent`, `judge`, and `probe` with `--probe`) is missing.
473
484
 
474
- ### In CI or a merge gate
485
+ ### Merge gate on the desktop
475
486
 
476
487
  ```sh
477
488
  PR_BRANCH="$(gh pr view "$PR_URL" --json headRefName --jq .headRefName)"
@@ -480,14 +491,35 @@ done-done receipt --from - --trusted-ref origin/main --claim-id "$PR_BRANCH" < p
480
491
  done-done receipt --verify "$HEAD" # re-verify later
481
492
  ```
482
493
 
483
- Run `receipt --verify` with that head checked out and the evidence root in place (see below). `--trusted-ref` reads `.done-done.yaml` from that ref instead of the PR branch. This matters because repo keys can loosen the bar and journeys run commands. `check --from -` and `receipt --from -` read the `## Checks` block from stdin, so a CI job can pipe the pull request body straight in.
494
+ Run `receipt --verify` with that head checked out and the evidence root in place (see below). `--trusted-ref` reads `.done-done.yaml` from that ref instead of the PR branch. This matters because repo keys can loosen the bar and journeys run commands. `check --from -` and `receipt --from -` read the `## Checks` block from stdin, so a script can pipe the pull request body straight in.
495
+
496
+ ### CI gate
497
+
498
+ A hosted runner has no model and never gets a model key, so CI never runs `check`, `run` or `verify`. Proof runs on the desktop. CI runs one no-model command, `done-done gate`, which reads git objects only: no config, no judge, no key and no evidence files.
499
+
500
+ ```sh
501
+ # on the desktop, after the proof passes for <head>
502
+ done-done receipt --from checks.md --claim-id "$BRANCH" # writes <evidence root>/.done-done/receipts/<head>.json
503
+ mkdir -p .done-done-receipts && cp <that receipt> .done-done-receipts/
504
+ git add .done-done-receipts && git commit -m "chore: receipt for <head>" && git push
505
+
506
+ # in CI (checkout with fetch-depth: 0, so the receipt head is in history)
507
+ npx -y done-done-axi gate "$PR_HEAD_SHA"
508
+ ```
509
+
510
+ The receipt is committed in a follow-up commit, because a receipt cannot sit inside the commit it proves (its hash would change). `gate [<head>] [--dir <folder>] [--pushed <time>]` prints `PASS` or `FAIL: <reason>` and exits 0, 1, or 2 on a usage error. It passes when a receipt in `.done-done-receipts/` (or `--dir`):
511
+
512
+ - is a well-formed pass from a judged run (the same shape `receipt --verify` requires) and is filed as `<its head>.json`;
513
+ - has a head that is `<head>` (default `HEAD`) or an ancestor of it, with every file changed since that head inside the receipts folder, so the proof still covers the code;
514
+ - was made no later than the push (`--pushed`, an ISO time; default the commit time of `<head>`), compared to the second.
515
+
516
+ `gate` trusts the committed JSON: it does not re-hash the evidence, which only `receipt --verify` can do, on the machine that holds it. It says so by name and does not claim more.
484
517
 
485
518
  ### Where proof lives
486
519
 
487
520
  Evidence, run records (`<root>/.done-done/runs/`), receipts (`<root>/.done-done/receipts/<head>.json`) and the reuse ledger (`<root>/.done-done/ledger`) all live in the evidence root. By default that is `~/.done-done/evidence/<app>`, outside the repo, so they outlive the checkout that captured them: any checkout of the same repo on the same machine (a fresh clone, a merge gate's checkout) with `<head>` checked out runs `done-done receipt --verify <head>` and finds the same receipt and files. A checkout of a later commit, such as the merge or squash commit on main, does not verify: the receipt is for `<head>` only.
488
521
 
489
522
  - **Same machine** (a merge gate, a fresh clone): check out `<head>`, nothing else. Every checkout reads the same `app` from the committed `.done-done.yaml`, so it resolves the same evidence root. Keep `app` pinned (`init` writes it); without it the app is the directory name, which differs between clones. Receipts are named by the full head sha, which is the same in every clone, shallow or not, so no repo commit is needed in the key, and a receipt verifies only where that commit is checked out.
490
- - **CI on another machine**: the evidence is not there. Copy the evidence root to the job (for example upload `~/.done-done/evidence/<app>` as a build artifact from the job that captured the proof), restore it as `$DONE_DONE_HOME/evidence/<app>`, check out the head, and run `done-done receipt --verify <head>`.
491
523
  - **`evidence.root` inside the repo** (an older `root: .proof`): still works, but the evidence is gitignored and goes away with that checkout, so only that checkout can re-verify.
492
524
 
493
525
  ## Config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "done-done-axi",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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",
@@ -29,7 +29,9 @@ First run `command -v done-done`. If it prints a path, run `done-done` as shown
29
29
  - `done-done <command> --help` for one command's usage and flags
30
30
 
31
31
  Results are TOON: read `result` and follow the `help` lines through
32
- `init`, `doctor`, `run`, `verify`, `check` and `receipt`. Proof comes from driving the real product, not from a unit test run, and counts only from a committed
32
+ `init`, `doctor`, `run`, `verify`, `check` and `receipt`. `design` judges captured screens for visual defects,
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, not from a unit test run, and counts only from a committed
33
35
  tree: commit the done-done setup (config, capture script) before you capture. `fail` or
34
36
  `inconclusive` is not done: fix the cause and re-run, or tell the user exactly what is still unproven.
35
37
  A run waiting at its judge gate (`awaiting_approval`) did not pass, and nothing moves it past its judge: show the user
@@ -46,6 +48,18 @@ journeys:
46
48
  command: done-done look http://127.0.0.1:3000/inbox --out "$DONE_DONE_EVIDENCE_ROOT/inbox.png"
47
49
  ```
48
50
 
51
+ To gate a worker's claim cheaply, `done-done lint <PR URL | report file> <evidence folder>` reads the newest `## Checks` block
52
+ with no judge or model and prints `PASS` or `FAIL: <reason>` (exit 1 on a fail): missing lines, evidence that is absent,
53
+ empty, stale, duplicated or already used by another PR, a n/a repro on a fix, or a PR of 1,000+ added lines all fail.
54
+ `evidence: not proven: <cause>` passes. Fix the cause it names and re-run; never edit the evidence to get past it.
55
+
56
+ A CI job gets no model and no key, so it never runs `check`, `run` or `verify`. Proof runs on the desktop, then commit the
57
+ receipt it wrote (`<evidence root>/.done-done/receipts/<head>.json`) into `.done-done-receipts/` in a follow-up commit. CI runs only
58
+ `done-done gate <head>` (full history): `PASS` or `FAIL: <reason>` for a missing, late, other-head or moved-on receipt.
59
+
60
+ `init` also has that agent write the repo's `.agents/skills/verify-<app>/SKILL.md` (never overwriting one): read it before
61
+ you drive the app, and fix it when it is wrong.
62
+
49
63
  done-done starts a coding agent the user chose. When it says no agent is set, ask the user which agent
50
64
  (and model) to use and save their answer with the command it prints; never choose one for them.
51
65
 
@@ -1,7 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="362" height="64" viewBox="0 0 362 64" role="img" aria-label="done-done">
2
- <title>done-done</title>
3
- <defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#3fb950"/><stop offset="1" stop-color="#1a7f37"/></linearGradient></defs>
4
- <rect width="64" height="64" rx="16" fill="url(#g)"/>
5
- <g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 34l9 9 17-20"/><path d="M33 40l3 3 17-20"/></g>
6
- <g transform="translate(86 49)"><path fill="#e6edf3" d="M14 0.6Q9.5 0.6 6.7 -2.6Q4 -5.8 4 -11.2V-19.5Q4 -25 6.7 -28.2Q9.5 -31.4 14 -31.4Q17.2 -31.4 19.4 -29.7Q21.6 -28.1 22.5 -25.2L22.3 -32.3V-40.9H29.3V0H22.5V-5.6Q21.6 -2.7 19.4 -1.1Q17.2 0.6 14 0.6ZM16.6 -5.5Q19.3 -5.5 20.8 -7.1Q22.3 -8.7 22.3 -11.7V-19.1Q22.3 -22.1 20.8 -23.7Q19.3 -25.3 16.6 -25.3Q13.9 -25.3 12.5 -23.9Q11 -22.4 11 -19.4V-11.4Q11 -8.4 12.5 -6.9Q13.9 -5.5 16.6 -5.5ZM46.9 0.5Q41 0.5 37.5 -2.8Q34 -6.2 34 -11.8V-19Q34 -24.7 37.5 -28Q41 -31.3 46.9 -31.3Q52.8 -31.3 56.3 -28Q59.8 -24.7 59.8 -19V-11.8Q59.8 -6.2 56.3 -2.8Q52.8 0.5 46.9 0.5ZM46.9 -5.6Q49.7 -5.6 51.2 -7.1Q52.8 -8.7 52.8 -11.6V-19.2Q52.8 -22.1 51.2 -23.7Q49.7 -25.2 46.9 -25.2Q44.1 -25.2 42.6 -23.7Q41 -22.1 41 -19.2V-11.6Q41 -8.7 42.6 -7.1Q44.1 -5.6 46.9 -5.6ZM64.5 0V-30.8H71.3V-25Q71.9 -27.9 74.1 -29.7Q76.3 -31.4 79.6 -31.4Q84.2 -31.4 86.9 -28.3Q89.6 -25.3 89.6 -20.2V0H82.6V-19.4Q82.6 -22.2 81.1 -23.8Q79.6 -25.3 77.1 -25.3Q74.5 -25.3 73 -23.7Q71.5 -22.2 71.5 -19.3V0ZM107.2 0.6Q103.2 0.6 100.3 -1Q97.4 -2.5 95.8 -5.2Q94.2 -8 94.2 -11.8V-19Q94.2 -22.8 95.8 -25.6Q97.4 -28.3 100.3 -29.8Q103.2 -31.4 107.2 -31.4Q111 -31.4 113.9 -29.8Q116.8 -28.3 118.4 -25.6Q120 -22.9 120 -19.3V-13.7H100.9V-11.5Q100.9 -8.4 102.6 -6.8Q104.2 -5.2 107.2 -5.2Q109.3 -5.2 110.9 -6Q112.4 -6.7 112.7 -8.2H119.6Q118.9 -4.2 115.5 -1.8Q112.1 0.6 107.2 0.6ZM100.9 -19.3V-18.2L113.3 -18.3V-19.3Q113.3 -22.5 111.7 -24.2Q110.1 -26 107.2 -26Q104.1 -26 102.5 -24.2Q100.9 -22.5 100.9 -19.3ZM128.2 -15.2V-21.7H146.2V-15.2Z"/><path fill="#3fb950" d="M164.5 0.6Q160 0.6 157.2 -2.6Q154.5 -5.8 154.5 -11.2V-19.5Q154.5 -25 157.2 -28.2Q160 -31.4 164.5 -31.4Q167.7 -31.4 169.9 -29.7Q172.1 -28.1 173 -25.2L172.8 -32.3V-40.9H179.8V0H173V-5.6Q172.1 -2.7 169.9 -1.1Q167.7 0.6 164.5 0.6ZM167.1 -5.5Q169.8 -5.5 171.3 -7.1Q172.8 -8.7 172.8 -11.7V-19.1Q172.8 -22.1 171.3 -23.7Q169.8 -25.3 167.1 -25.3Q164.4 -25.3 163 -23.9Q161.5 -22.4 161.5 -19.4V-11.4Q161.5 -8.4 163 -6.9Q164.4 -5.5 167.1 -5.5ZM197.4 0.5Q191.5 0.5 188 -2.8Q184.5 -6.2 184.5 -11.8V-19Q184.5 -24.7 188 -28Q191.5 -31.3 197.4 -31.3Q203.3 -31.3 206.8 -28Q210.3 -24.7 210.3 -19V-11.8Q210.3 -6.2 206.8 -2.8Q203.3 0.5 197.4 0.5ZM197.4 -5.6Q200.2 -5.6 201.7 -7.1Q203.3 -8.7 203.3 -11.6V-19.2Q203.3 -22.1 201.7 -23.7Q200.2 -25.2 197.4 -25.2Q194.6 -25.2 193.1 -23.7Q191.5 -22.1 191.5 -19.2V-11.6Q191.5 -8.7 193.1 -7.1Q194.6 -5.6 197.4 -5.6ZM215 0V-30.8H221.8V-25Q222.4 -27.9 224.6 -29.7Q226.8 -31.4 230.1 -31.4Q234.7 -31.4 237.4 -28.3Q240.1 -25.3 240.1 -20.2V0H233.1V-19.4Q233.1 -22.2 231.6 -23.8Q230.1 -25.3 227.6 -25.3Q225 -25.3 223.5 -23.7Q222 -22.2 222 -19.3V0ZM257.7 0.6Q253.7 0.6 250.8 -1Q247.9 -2.5 246.3 -5.2Q244.7 -8 244.7 -11.8V-19Q244.7 -22.8 246.3 -25.6Q247.9 -28.3 250.8 -29.8Q253.7 -31.4 257.7 -31.4Q261.5 -31.4 264.4 -29.8Q267.3 -28.3 268.9 -25.6Q270.5 -22.9 270.5 -19.3V-13.7H251.4V-11.5Q251.4 -8.4 253.1 -6.8Q254.7 -5.2 257.7 -5.2Q259.8 -5.2 261.4 -6Q262.9 -6.7 263.2 -8.2H270.1Q269.4 -4.2 266 -1.8Q262.6 0.6 257.7 0.6ZM251.4 -19.3V-18.2L263.8 -18.3V-19.3Q263.8 -22.5 262.2 -24.2Q260.6 -26 257.7 -26Q254.6 -26 253 -24.2Q251.4 -22.5 251.4 -19.3Z"/></g>
7
- </svg>
@@ -1,7 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="362" height="64" viewBox="0 0 362 64" role="img" aria-label="done-done">
2
- <title>done-done</title>
3
- <defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#3fb950"/><stop offset="1" stop-color="#1a7f37"/></linearGradient></defs>
4
- <rect width="64" height="64" rx="16" fill="url(#g)"/>
5
- <g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 34l9 9 17-20"/><path d="M33 40l3 3 17-20"/></g>
6
- <g transform="translate(86 49)"><path fill="#1f2328" d="M14 0.6Q9.5 0.6 6.7 -2.6Q4 -5.8 4 -11.2V-19.5Q4 -25 6.7 -28.2Q9.5 -31.4 14 -31.4Q17.2 -31.4 19.4 -29.7Q21.6 -28.1 22.5 -25.2L22.3 -32.3V-40.9H29.3V0H22.5V-5.6Q21.6 -2.7 19.4 -1.1Q17.2 0.6 14 0.6ZM16.6 -5.5Q19.3 -5.5 20.8 -7.1Q22.3 -8.7 22.3 -11.7V-19.1Q22.3 -22.1 20.8 -23.7Q19.3 -25.3 16.6 -25.3Q13.9 -25.3 12.5 -23.9Q11 -22.4 11 -19.4V-11.4Q11 -8.4 12.5 -6.9Q13.9 -5.5 16.6 -5.5ZM46.9 0.5Q41 0.5 37.5 -2.8Q34 -6.2 34 -11.8V-19Q34 -24.7 37.5 -28Q41 -31.3 46.9 -31.3Q52.8 -31.3 56.3 -28Q59.8 -24.7 59.8 -19V-11.8Q59.8 -6.2 56.3 -2.8Q52.8 0.5 46.9 0.5ZM46.9 -5.6Q49.7 -5.6 51.2 -7.1Q52.8 -8.7 52.8 -11.6V-19.2Q52.8 -22.1 51.2 -23.7Q49.7 -25.2 46.9 -25.2Q44.1 -25.2 42.6 -23.7Q41 -22.1 41 -19.2V-11.6Q41 -8.7 42.6 -7.1Q44.1 -5.6 46.9 -5.6ZM64.5 0V-30.8H71.3V-25Q71.9 -27.9 74.1 -29.7Q76.3 -31.4 79.6 -31.4Q84.2 -31.4 86.9 -28.3Q89.6 -25.3 89.6 -20.2V0H82.6V-19.4Q82.6 -22.2 81.1 -23.8Q79.6 -25.3 77.1 -25.3Q74.5 -25.3 73 -23.7Q71.5 -22.2 71.5 -19.3V0ZM107.2 0.6Q103.2 0.6 100.3 -1Q97.4 -2.5 95.8 -5.2Q94.2 -8 94.2 -11.8V-19Q94.2 -22.8 95.8 -25.6Q97.4 -28.3 100.3 -29.8Q103.2 -31.4 107.2 -31.4Q111 -31.4 113.9 -29.8Q116.8 -28.3 118.4 -25.6Q120 -22.9 120 -19.3V-13.7H100.9V-11.5Q100.9 -8.4 102.6 -6.8Q104.2 -5.2 107.2 -5.2Q109.3 -5.2 110.9 -6Q112.4 -6.7 112.7 -8.2H119.6Q118.9 -4.2 115.5 -1.8Q112.1 0.6 107.2 0.6ZM100.9 -19.3V-18.2L113.3 -18.3V-19.3Q113.3 -22.5 111.7 -24.2Q110.1 -26 107.2 -26Q104.1 -26 102.5 -24.2Q100.9 -22.5 100.9 -19.3ZM128.2 -15.2V-21.7H146.2V-15.2Z"/><path fill="#1a7f37" d="M164.5 0.6Q160 0.6 157.2 -2.6Q154.5 -5.8 154.5 -11.2V-19.5Q154.5 -25 157.2 -28.2Q160 -31.4 164.5 -31.4Q167.7 -31.4 169.9 -29.7Q172.1 -28.1 173 -25.2L172.8 -32.3V-40.9H179.8V0H173V-5.6Q172.1 -2.7 169.9 -1.1Q167.7 0.6 164.5 0.6ZM167.1 -5.5Q169.8 -5.5 171.3 -7.1Q172.8 -8.7 172.8 -11.7V-19.1Q172.8 -22.1 171.3 -23.7Q169.8 -25.3 167.1 -25.3Q164.4 -25.3 163 -23.9Q161.5 -22.4 161.5 -19.4V-11.4Q161.5 -8.4 163 -6.9Q164.4 -5.5 167.1 -5.5ZM197.4 0.5Q191.5 0.5 188 -2.8Q184.5 -6.2 184.5 -11.8V-19Q184.5 -24.7 188 -28Q191.5 -31.3 197.4 -31.3Q203.3 -31.3 206.8 -28Q210.3 -24.7 210.3 -19V-11.8Q210.3 -6.2 206.8 -2.8Q203.3 0.5 197.4 0.5ZM197.4 -5.6Q200.2 -5.6 201.7 -7.1Q203.3 -8.7 203.3 -11.6V-19.2Q203.3 -22.1 201.7 -23.7Q200.2 -25.2 197.4 -25.2Q194.6 -25.2 193.1 -23.7Q191.5 -22.1 191.5 -19.2V-11.6Q191.5 -8.7 193.1 -7.1Q194.6 -5.6 197.4 -5.6ZM215 0V-30.8H221.8V-25Q222.4 -27.9 224.6 -29.7Q226.8 -31.4 230.1 -31.4Q234.7 -31.4 237.4 -28.3Q240.1 -25.3 240.1 -20.2V0H233.1V-19.4Q233.1 -22.2 231.6 -23.8Q230.1 -25.3 227.6 -25.3Q225 -25.3 223.5 -23.7Q222 -22.2 222 -19.3V0ZM257.7 0.6Q253.7 0.6 250.8 -1Q247.9 -2.5 246.3 -5.2Q244.7 -8 244.7 -11.8V-19Q244.7 -22.8 246.3 -25.6Q247.9 -28.3 250.8 -29.8Q253.7 -31.4 257.7 -31.4Q261.5 -31.4 264.4 -29.8Q267.3 -28.3 268.9 -25.6Q270.5 -22.9 270.5 -19.3V-13.7H251.4V-11.5Q251.4 -8.4 253.1 -6.8Q254.7 -5.2 257.7 -5.2Q259.8 -5.2 261.4 -6Q262.9 -6.7 263.2 -8.2H270.1Q269.4 -4.2 266 -1.8Q262.6 0.6 257.7 0.6ZM251.4 -19.3V-18.2L263.8 -18.3V-19.3Q263.8 -22.5 262.2 -24.2Q260.6 -26 257.7 -26Q254.6 -26 253 -24.2Q251.4 -22.5 251.4 -19.3Z"/></g>
7
- </svg>