done-done-axi 0.6.9 → 0.6.11

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
@@ -37,6 +37,8 @@ You need **[Node 22.18+](https://nodejs.org) and git**, plus your own signed-in
37
37
  2. **Set up your repo.** `init` asks you, in a terminal, which coding agent drives your app and which judge checks it. done-done never picks either for you. It then writes a minimal `.done-done.yaml` and a verify skill: how to launch and drive your app. Outside a terminal, pass `--agent <agent> --model <model> --judge <agent> --judge-model <model>` instead.
38
38
  3. **Get your first result.** Say what works now, in plain words. `verify` has your agent drive the real product the way a user does and save screenshots or output. A fresh judge checks whether each capture shows your claim. A pass names the capture and writes a receipt for this commit. Anything else stops at a gate with the judge's reason, and `done-done respond` answers it. No judge pass, no receipt.
39
39
 
40
+ `done-done --version` prints the bare version first; help headers show it too, and suffixed versions are labelled `pre-release`.
41
+
40
42
  Optional setup check: `done-done doctor --probe` requires a real answer from your chosen agent; it checks setup, not whether your app works.
41
43
 
42
44
  For a signed-in Claude Code setup, pass `--agent claude --model claude-haiku-5-5 --judge claude --judge-model claude-haiku-5-5` to `init`. For Codex, use `--agent codex --model gpt-6.1-sol` with that same judge. Codex readers and probes require a CLI with `exec --ignore-user-config` and `debug models --bundled`: they keep your login, but disable MCP, project instructions and model-provided tools; edit/drive steps keep your normal Codex config. Codex is not a judge route.
@@ -192,11 +194,15 @@ That first run is a trial on uncommitted files, not proof. Read the generated sc
192
194
 
193
195
  Every answer is an exit code: **0** pass, **1** fail, **2** usage or config error (including no agent or judge set, a check that fails before its judge, a capture the judge cannot be sent, or a judge that did not answer: never a result), **3** can't tell either way, **4** a `verify`, `maintain` or `rerun` run is still running, or a `verify` run waits at its gate.
194
196
 
197
+ ### Interaction polish needs proof too
198
+
199
+ Opt in with `interaction_checklist` in `.done-done.yaml`: detail next/previous plus swipe, instant send/answer state, destructive undo, scroll position on back, long-list search/filter, and useful empty states. Each item needs judged real-product frames or an explicit not-applicable reason; missing proof is flagged, never silently passed. The [shipped checklist and wiring](docs/reference.md#interaction-checklist-opt-in) extend `check`, `receipt` and `verify`; existing approved-design checks stay unchanged without opt-in.
200
+
195
201
  ## Alone, and with the other tools
196
202
 
197
203
  done-done works on its own: the CLI and the skill need nothing else. It is one of three tools that each do one job and connect through small written contracts ([Vision](VISION.md)):
198
204
 
199
- - [`sign-off-axi`](https://www.npmjs.com/package/sign-off-axi): the board where agents ask you questions. `done-done signoff` writes a run's gate (when the judge was unsure) or its evidence in sign-off's message format and prints the post command; it never posts on its own. With the opt-in (`done-done signoff --post`, or `signoff.post: true` in `.done-done.yaml`) it posts that message to the board through sign-off-axi, records the card on the run, and `done-done signoff --wait` brings the answer back to the same run. Without sign-off you answer at the terminal with `done-done respond`.
205
+ - [`sign-off-axi`](https://www.npmjs.com/package/sign-off-axi): the board where agents ask you questions. `done-done signoff` prepares a run's gate (when the judge was unsure) or evidence in sign-off's message format and prints the post command. done-done itself does not publish to a board; sign-off-axi posts the card. With the opt-in (`done-done signoff --post`, or `signoff.post: true` in `.done-done.yaml`), done-done hands the run to sign-off-axi, which posts the message and returns a card that done-done records. `done-done signoff --wait` brings the answer back to the same run. Without sign-off you answer at the terminal with `done-done respond`.
200
206
  - [`learn-from-mistakes-axi`](https://www.npmjs.com/package/learn-from-mistakes-axi): the retro tool that finds mistakes your agents keep repeating, with evidence. Neither tool needs the other.
201
207
  - [`first-draft-axi`](https://www.npmjs.com/package/first-draft-axi): turns a prompt into design mocks. Pass a mock to `done-done design <screen> --mock <screen>=<mock>` to judge a captured screen next to it.
202
208
 
package/dist/cli.js CHANGED
@@ -10,12 +10,12 @@ import { parseArgs } from 'node:util';
10
10
  import { encode } from '@toon-format/toon';
11
11
  import { Document, parseDocument } from 'yaml';
12
12
  import { ACPX_INSTALL, agentChoices, agentPath, agentRun, askAgent, findBin, JUDGES, modelFor, noAgent, noJudge, notStartable, routeOf, selectAgent, selectFallback, selectJudge, toollessReason } from './agents.js';
13
- import { AGENTS, CHEAP, globalConfigText, home, inputPattern, isAgent, isJudgeRoute, loadConfig, parseDuration, tilde, UsageError } from './config.js';
13
+ import { AGENTS, CHEAP, INTERACTIONS, globalConfigText, home, inputPattern, isAgent, isJudgeRoute, loadConfig, parseDuration, tilde, UsageError } from './config.js';
14
14
  import { backend, judge, judgeable, onPath, quote, screen } from './judge.js';
15
15
  import { startInstance, stopInstance } from './instance.js';
16
16
  import { attemptProblem, changedSince, commitFix, dirtyTracked, FIX_SCHEMA, inWorktree, maintainSteps, journeyProposal, named, pathspec, readerProblem, repoState, triage } from './maintain.js';
17
17
  import { cellEnv, cellName, cellSuffix, hasMatrix, matrixManifest, splitMatrix } from './matrix.js';
18
- import { design, KINDS } from './design.js';
18
+ import { design, interactions, KINDS } from './design.js';
19
19
  import { checkMedia } from './media.js';
20
20
  import { domIssues, domRecord, isDomRecord, lookIssues, SQUASHED_TEXT } from './dom.js';
21
21
  import { lint } from './lint.js';
@@ -25,6 +25,8 @@ import { mapChange } from './affected.js';
25
25
  import { codeOnly, codeOnlyJourneys, configFault, MARK, namedJourney, releaseArtifacts, RULE, runsFault, STEPS_FILE, STEPS_SHAPE, stepRecordFault, stepsFault } from './proof.js';
26
26
  const PASS = 0, FAIL = 1, USAGE = 2, INCONCLUSIVE = 3, WAITING = 4;
27
27
  const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
28
+ const PRE_RELEASE = /^[^+]*-/.test(VERSION);
29
+ const HELP_HEADER = `done-done ${VERSION}${PRE_RELEASE ? ' (pre-release)' : ''}`;
28
30
  // The shape of a journey, so a new user or agent can write one without opening the reference.
29
31
  // done-done sets DONE_DONE_EVIDENCE_ROOT (the run's own folder) and replaces ${RUN_ID}, so the example works for any evidence root.
30
32
  const journeyExample = 'journeys: { smoke: { command: "<drive the core user journey on the real app> > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log", core: true } }';
@@ -51,7 +53,11 @@ const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then
51
53
  proof: <the command or path that drove the real product; name the repo's done-done-verify-<app> skill, or say "no verify skill">
52
54
  evidence: <the evidence file paths, or "not proven: <cause>">
53
55
  approved_design (.done-done.yaml): a non-empty manifest of {screen, size: <width>x<height>, theme, mock: <repo PNG/JPEG>, capture: <unique filename>};
54
- every claim needs every listed live screen, paired with its mock and a passing design verdict; missing or unsure matches fail by screen/size/theme`;
56
+ every claim needs every listed live screen, paired with its mock and a passing design verdict; missing or unsure matches fail by screen/size/theme
57
+ interaction_checklist (.done-done.yaml, opt-in): map the six ids in skills/done-done/interaction-checklist.json to {captures: [<live PNG/JPEG filename>, ...]}
58
+ or {not_applicable: <reason>}; detail-navigation, instant-state, destructive-undo, scroll-restoration, list-search, empty-next-step.
59
+ check/receipt/verify report every item: pass only with judged real-product frames, not_applicable with a reason, otherwise flagged (exit 1).
60
+ Omitted checklist: unchanged behavior; interaction_checklist: {} flags all six. Frames must be cited from sealed runs; static controls alone do not prove an interaction`;
55
61
  const RUNS_TEXT = `runs: each verify or maintain run lives in $DONE_DONE_HOME/runs/<app>/<run-id> (run.json, events.jsonl, logs/<step>.log,
56
62
  agents/<n>-<name>.json for each agent call) and goes on when the command that started it ends;
57
63
  verify steps: resolve, drive, check, judge (always; skipped only after a failed check), triage and fix (only with auto_fix.verify above 0), gate, receipt, report;
@@ -342,9 +348,10 @@ each prints the files it changed and runs the journey once as a dirty-tree trial
342
348
  },
343
349
  };
344
350
  const rows = (pairs, width = Math.max(...pairs.map(([a]) => a.length)) + 3) => pairs.map(([a, b]) => ` ${a}${' '.repeat(Math.max(3, width - a.length))}${b}`).join('\n');
345
- const GLOBAL_TEXT = `global flags: --json (JSON instead of TOON), --full (do not truncate lists), --help (this command's usage), --version (done-done's version)
351
+ const GLOBAL_TEXT = `global flags: --json (JSON instead of TOON), --full (do not truncate lists), --help (this command's usage), --version (bare version first; pre-release label on the next line when suffixed)
346
352
  exit codes: 0 pass, 1 fail, 2 usage or config error, 3 inconclusive, 4 the run is still running or a verify run waits at its gate (verify, maintain, rerun, status, respond)`;
347
- const USAGE_TEXT = `install: npm install -g --prefix ~/.local done-done-axi (installs done-done-axi and done-done), or run npx -y done-done-axi <command>
353
+ const USAGE_TEXT = `${HELP_HEADER}
354
+ install: npm install -g --prefix ~/.local done-done-axi (installs done-done-axi and done-done), or run npx -y done-done-axi <command>
348
355
  usage:
349
356
  ${rows([['done-done', "show this repo's config state and the next step"], ...Object.values(COMMANDS).flatMap((c) => c.usage)], 46)}
350
357
  ${GLOBAL_TEXT}
@@ -365,7 +372,7 @@ Run \`done-done <command> --help\` for that command's flags.`;
365
372
  function commandHelp(cmd) {
366
373
  const c = COMMANDS[cmd];
367
374
  const flags = c.flags.length ? rows(c.flags.map((f) => [f, FLAGS[f]])) : ' none';
368
- return [`usage:\n${rows(c.usage)}`, `flags:\n${flags}`, ...c.more, GLOBAL_TEXT].join('\n');
375
+ return [HELP_HEADER, `usage:\n${rows(c.usage)}`, `flags:\n${flags}`, ...c.more, GLOBAL_TEXT].join('\n');
369
376
  }
370
377
  // How many list rows a compact result shows before it reports the rest as omitted.
371
378
  const COMPACT_LIST = 5;
@@ -1021,7 +1028,17 @@ async function runCheck(argv, run) {
1021
1028
  if (judgeBlock)
1022
1029
  judgeBlock.approved_design = matches;
1023
1030
  }
1031
+ const touches = await interactions(cfg, choice, captures, run ? join(run.dir, 'agents') : join(home(), 'agents'));
1032
+ for (const r of touches) {
1033
+ r.captures = r.captures.map(c => ({ ...c, path: receiptPath(repo, cfg, cite(c.path)) }));
1034
+ if (r.status === 'flagged')
1035
+ findings.push({ level: 'fail', why: `interaction checklist: ${r.id}: flagged: ${r.reason}` });
1036
+ }
1037
+ if (judgeBlock && touches.length)
1038
+ judgeBlock.interaction_checklist = touches;
1024
1039
  const rep = report(findings, files, { cfg, repo, claim: claim.outcome, source: values.from, citationBase: values['citation-base'], judge: judged, judgeLevel, warnings });
1040
+ if (touches.length)
1041
+ rep.result.interaction_checklist = touches;
1025
1042
  if (matches.length)
1026
1043
  rep.result.design = matches;
1027
1044
  if (rep.code === PASS) {
@@ -1067,6 +1084,11 @@ function receiptFault(rec, short) {
1067
1084
  return `receipt for ${short} has a malformed judge block`;
1068
1085
  if (j.approved_design !== undefined && (!Array.isArray(j.approved_design) || !j.approved_design.length || !j.approved_design.every(v => v && v.verdict === 'pass' && [v.screen, v.size, v.theme].every(s => typeof s === 'string' && s.trim()) && rec.files.some(f => f.path === v.path && f.sha256 === v.sha256) && rec.files.some(f => f.path === v.mock && f.sha256 === v.mock_sha256))))
1069
1086
  return `receipt for ${short} has a malformed approved-design verdict`;
1087
+ if (j.interaction_checklist !== undefined) {
1088
+ const rows = j.interaction_checklist;
1089
+ if (!Array.isArray(rows) || rows.length !== Object.keys(INTERACTIONS).length || new Set(rows.map(r => r?.id)).size !== rows.length || !rows.every(r => r && Object.hasOwn(INTERACTIONS, r.id) && typeof r.reason === 'string' && r.reason.trim() && Array.isArray(r.captures) && (r.status === 'not_applicable' ? !r.captures.length : r.status === 'pass' && r.captures.length && r.captures.every(c => c && rec.files.some(f => f.path === c.path && f.sha256 === c.sha256)))))
1090
+ return `receipt for ${short} has a malformed interaction-checklist verdict`;
1091
+ }
1070
1092
  const code = runsFault(rec.runs);
1071
1093
  if (code)
1072
1094
  return `receipt for ${short} is no real-product proof: ${code}`;
@@ -2772,7 +2794,7 @@ async function cmdSignoff(argv) {
2772
2794
  writeFileSync(file, `${JSON.stringify(msg, null, 2)}\n`);
2773
2795
  const at = citable(repo, file);
2774
2796
  if (!posts) {
2775
- emit({ command: 'signoff', run_id: id, kind: msg.kind, file: at, help: [`Post it to the sign-off board (done-done never posts): npx -y sign-off-axi post --kind ${msg.kind} --from ${arg(at)}`] });
2797
+ emit({ command: 'signoff', run_id: id, kind: msg.kind, file: at, help: [`Post it to the sign-off board with sign-off-axi (done-done prepares the run; sign-off-axi posts): npx -y sign-off-axi post --kind ${msg.kind} --from ${arg(at)}`] });
2776
2798
  return PASS;
2777
2799
  }
2778
2800
  // Post it through the board's own CLI and record the card it made, so the claim traces to the run and its card.
@@ -4525,7 +4547,7 @@ async function main(argv) {
4525
4547
  return PASS;
4526
4548
  }
4527
4549
  if (cmd === '--version') {
4528
- console.log(VERSION);
4550
+ console.log(VERSION + (PRE_RELEASE ? '\npre-release' : ''));
4529
4551
  return PASS;
4530
4552
  }
4531
4553
  if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
package/dist/config.js CHANGED
@@ -12,6 +12,7 @@ export class UsageError extends Error {
12
12
  this.help = help;
13
13
  }
14
14
  }
15
+ export const INTERACTIONS = JSON.parse(readFileSync(new URL('../skills/done-done/interaction-checklist.json', import.meta.url), 'utf8'));
15
16
  // The coding agents a user can set as agent.
16
17
  export const AGENTS = ['claude', 'codex', 'opencode', 'pi', 'gemini'];
17
18
  // The agents whose CLI can read an attached image with every tool off, so they judge through the answerer route;
@@ -68,6 +69,7 @@ const KEYS = {
68
69
  dom: { scope: 'repo', type: 'dom' },
69
70
  look: { scope: 'repo', type: 'look' },
70
71
  approved_design: { scope: 'repo', type: 'approved_design' },
72
+ interaction_checklist: { scope: 'repo', type: 'interaction_checklist' },
71
73
  base_ref: { scope: 'repo', type: 'string' },
72
74
  'evidence.root': { scope: 'repo', type: 'string' },
73
75
  'evidence.allow_dirty': { scope: 'repo', type: 'boolean' },
@@ -214,6 +216,25 @@ function apply(target, text, file, scope) {
214
216
  target[key] = { command: v.command, ready: has(r.url) ? { url: r.url } : { command: r.command }, ready_timeout: v.ready_timeout ?? '30s' };
215
217
  continue;
216
218
  }
219
+ if (key === 'interaction_checklist') {
220
+ const fail = () => { throw new UsageError(`${file}: interaction_checklist needs known checklist ids, each with only captures: [<PNG/JPEG filename>, ...] (1-8), or not_applicable: <non-empty reason>`); };
221
+ if (!isMap(value))
222
+ fail();
223
+ for (const [id, row] of Object.entries(value)) {
224
+ if (!Object.hasOwn(INTERACTIONS, id) || !isMap(row) || Object.keys(row).length !== 1)
225
+ fail();
226
+ const r = row;
227
+ if (Object.hasOwn(r, 'not_applicable')) {
228
+ if (typeof r.not_applicable !== 'string' || !r.not_applicable.trim())
229
+ fail();
230
+ }
231
+ else if (!Array.isArray(r.captures) || !r.captures.length || r.captures.length > 8 || new Set(r.captures).size !== r.captures.length || !r.captures.every(p => typeof p === 'string' && !/[\\/\\\\]/.test(p) && p !== '..' && /\.(png|jpe?g)$/i.test(p)))
232
+ fail();
233
+ }
234
+ target.interaction_checklist = value;
235
+ target.from[key] = scope;
236
+ continue;
237
+ }
217
238
  if (key === 'approved_design') {
218
239
  const fail = () => { throw new UsageError(`${file}: approved_design must be a non-empty list of unique {screen, size: <width>x<height> in pixels, theme, mock: <repo-relative PNG/JPEG>, capture: <unique PNG/JPEG filename>}`); };
219
240
  const relativeImage = (p) => typeof p === 'string' && !isAbsolute(p) && !p.split(/[\\/]/).includes('..') && /\.(png|jpe?g)$/i.test(p);
@@ -314,6 +335,7 @@ function defaultConfig(repo) {
314
335
  dom: null,
315
336
  look: null,
316
337
  approved_design: [],
338
+ interaction_checklist: null,
317
339
  base_ref: '',
318
340
  evidence: { local_root: join(home(), 'evidence'), root: '', allow_dirty: false, ignore_paths: [] },
319
341
  secrets: { patterns: {}, allow: [] },
package/dist/design.js CHANGED
@@ -1,8 +1,52 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { join } from 'node:path';
3
- import { home, UsageError } from './config.js';
3
+ import { home, INTERACTIONS, UsageError } from './config.js';
4
4
  import { askJudge, backend, prepare, readCache, screen, VERSION, writeCache } from './judge.js';
5
5
  import { decodePixels } from './media.js';
6
+ // Only sealed, cited product frames reach here. Missing evidence is a finding, never a model-inferred pass.
7
+ export async function interactions(cfg, choice, captures, records) {
8
+ if (cfg.interaction_checklist === null)
9
+ return [];
10
+ const out = [];
11
+ for (const [id, requirement] of Object.entries(INTERACTIONS)) {
12
+ const entry = cfg.interaction_checklist[id];
13
+ const row = { id, status: 'flagged', reason: 'no real-product frame cited for this item', captures: [] };
14
+ if (entry?.not_applicable) {
15
+ row.status = 'not_applicable';
16
+ row.reason = entry.not_applicable;
17
+ }
18
+ else if (entry?.captures) {
19
+ const frames = entry.captures.map(name => captures.filter(c => c.name.split(/[\\/\\\\]/).at(-1) === name && screen(c)));
20
+ if (frames.some(f => f.length !== 1))
21
+ row.reason = 'each configured frame must name exactly one cited, run-bound PNG/JPEG: ' + entry.captures.join(', ');
22
+ else {
23
+ const shots = frames.map(f => f[0]);
24
+ row.captures = shots.map(c => ({ path: c.name, sha256: c.sha256 }));
25
+ const images = Object.fromEntries(shots.map((c, i) => {
26
+ const img = image(c);
27
+ if (typeof img === 'string')
28
+ throw new UsageError(`interaction checklist: ${img}`);
29
+ return [`frame_${i}`, img];
30
+ }));
31
+ const q = { interaction: { kind: 'choice', instructions: `Check this interaction on the real product: ${requirement} Frames are in action order. Describe visible evidence for each part before answering. Do not infer working behavior, timing, swipe or scroll restoration from a control's presence or a final state. Missing or insufficient evidence is missing, never shown.`, options: { shown: 'Every required part is demonstrated by these real-product frames.', missing: 'A required part is absent, broken or cannot be established from these frames.' }, images: Object.keys(images), floor: cfg.judge.floor, personReason: true } };
32
+ const key = createHash('sha256').update(JSON.stringify([VERSION, q, row.captures, choice.route, choice.model])).digest('hex');
33
+ const dir = join(home(), 'runs', cfg.app, 'interactions');
34
+ let hit = readCache(dir, key);
35
+ if (!hit || !['pass', 'flagged'].includes(hit.status) || typeof hit.reason !== 'string') {
36
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
37
+ const b = await backend(cfg, choice, n => `${records}/interaction-${stamp}-${id}-${n}.agent.json`, q, ASK_ATTEMPTS);
38
+ const a = (await askJudge(cfg, b, choice, { item: id, frames: entry.captures }, q, images)).interaction;
39
+ hit = { status: !a.abstained && a.answer === 'shown' ? 'pass' : 'flagged', reason: a.personReason || a.rationale || 'interaction not demonstrated' };
40
+ writeCache(dir, key, hit);
41
+ }
42
+ row.status = hit.status;
43
+ row.reason = hit.reason;
44
+ }
45
+ }
46
+ out.push(row);
47
+ }
48
+ return out;
49
+ }
6
50
  // The defect kinds, each its own question. Each asks for something the judge can see and measure, not whether it was
7
51
  // meant that way: a cheap model talks itself out of a judgment ("an intentional style", "the viewer's border"), not out of
8
52
  // a measurement. look is what the judge writes in its rationale before it answers.
package/docs/reference.md CHANGED
@@ -544,6 +544,24 @@ After those checks pass, each live image is sent beside its approved mock throug
544
544
 
545
545
  With no manifest, claims behave as before. `design` and `look --mock` remain ad-hoc measurements of supplied screens, not coverage claims: use `check`, `receipt` or `verify` to prove the complete approved inventory. This checks the declared inventory; it cannot discover an approved shot omitted from the manifest or establish capture authenticity beyond the existing run record.
546
546
 
547
+ #### Interaction checklist (opt-in)
548
+
549
+ Visual comparisons do not prove interaction polish. The package ships six requirements in `skills/done-done/interaction-checklist.json`: `detail-navigation` (next/previous **and swipe**), `instant-state` (immediate feedback on every send/answer), `destructive-undo` (undo restores content), `scroll-restoration` (back keeps list position), `list-search` (search/filter on long lists), and `empty-next-step` (empty states explain what to do next).
550
+
551
+ Wire the checklist in the repo's committed `.done-done.yaml`; it works with or without `approved_design`:
552
+
553
+ ```yaml
554
+ interaction_checklist:
555
+ empty-next-step: { captures: [empty.png] }
556
+ list-search: { captures: [list.png, filtered.png] }
557
+ detail-navigation: { not_applicable: "This product has no detail views" }
558
+ # Map the other three items too; omitted items are flagged, not passed.
559
+ ```
560
+
561
+ `check`, `receipt` and `verify` report all six under `interaction_checklist`, with `id`, `status`, `reason` and bound capture paths/hashes. Each entry accepts **only** `captures` (1–8 unique PNG/JPEG filenames, in action order) or `not_applicable` (a non-empty surface-specific reason). Capture real product frames through the project's recipe and cite them from sealed runs. Missing, ambiguous or uncited frames, an omitted item, a judge rejection or an unsure answer produce **flagged findings (exit 1)**. A pass requires the judge to tie every required part to those frames; a static control or final reply alone cannot prove swipe, timing, undo or scroll restoration. Include before/action/after frames where needed; insufficient visual evidence stays flagged. There is no automatic video-frame extraction.
562
+
563
+ A surface that does not exist must be explicitly `not_applicable` with its reason, never silently passed. Applicability is a declared inventory, not automatic surface discovery: reviewers must check those reasons and enumerate all relevant actions. Receipts preserve all six results under `judge.interaction_checklist`; passing frame hashes are bound to cited evidence, and receipt/gate validation rejects flagged or malformed entries. Without this config key, existing approved-design behavior is unchanged. `interaction_checklist: {}` opts in and flags all six; ad-hoc `design` is still only a visual check.
564
+
547
565
  ### Record a receipt
548
566
 
549
567
  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.
@@ -759,6 +777,7 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
759
777
  | `matrix` | none, so each journey runs once; `theme`, `width`, `form` and `skip` set the [capture matrix](#capture-matrix) |
760
778
  | `dom` | none; page-check records per capture, see [DOM assertions](#dom-assertions) |
761
779
  | `look` | none; the look step's approved design reference (`tokens`, `max_control`), see [Look check](#look-check) |
780
+ | `interaction_checklist` | none; opt-in six-item interaction evidence map; omitted/unproven items flagged, absent surfaces need reasons; see [Interaction checklist](#interaction-checklist-opt-in) |
762
781
  | `approved_design` | none; committed screen × pixel size × theme manifest of mocks and required live filenames; gates all claims, see [Approved-design coverage](#approved-design-coverage) |
763
782
  | `inputs` | `[]`; repo-relative paths or globs the drive must leave alone, see [Input guard](#input-guard) |
764
783
  | `base_ref` | none; a ref `verify` also drives, pairing its captures with the head's, see [Base-ref before](#base-ref-before) |
package/docs/releasing.md CHANGED
@@ -1,5 +1,5 @@
1
- # 0.6.9 patch release
1
+ # 0.6.11 patch release
2
2
 
3
- This release treats readable one-line ellipses as deliberate when at least 3 words or 20 characters remain visible, and checks each cited image on its own so swapped images cannot support a false claim.
3
+ This release shows the running version in CLI help headers, labels prereleases without changing the bare `--version` line, and clarifies the signoff posting hint.
4
4
 
5
- Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.9 version dist.integrity` and smoke-test a clean `npx` install with `done-done --version`. Do not run `npm publish` manually unless the workflow fails and the release is blocked; if so, stop and report.
5
+ Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.11 version dist.integrity` and smoke-test a clean `npx` install with `done-done --version`. Do not run `npm publish` manually unless the workflow fails and the release is blocked; if so, stop and report.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "done-done-axi",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
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",
@@ -27,6 +27,7 @@ First run `command -v done-done`. If it prints a path, run `done-done` as shown
27
27
  - `done-done` (no arguments, inside the repo) for this repo's proof setup and the next step
28
28
  - `done-done --help` for every command and flag, the journey format and the exact `## Checks` block
29
29
  - `done-done <command> --help` for one command's usage and flags
30
+ - `done-done --version` prints the bare version first; help headers show it too, and suffixed versions are labelled `pre-release`.
30
31
 
31
32
  Results are TOON: read `result` and follow the `help` lines through
32
33
  `init`, `doctor`, `run`, `verify`, `check` and `receipt`. `design` judges captured screens for visual defects,
@@ -69,6 +70,13 @@ journeys:
69
70
  command: done-done look http://127.0.0.1:3000/inbox --out "$DONE_DONE_EVIDENCE_ROOT/inbox.png"
70
71
  ```
71
72
 
73
+ Interaction coverage is opt-in via `interaction_checklist` in the repo config. Read the shipped `skills/done-done/interaction-checklist.json`:
74
+ next/previous plus swipe in details, instant send/answer feedback, destructive undo, list scroll restoration, long-list search/filter,
75
+ and empty-state next steps. Map every item to real-product frame filenames (`captures`, in action order) or `not_applicable`
76
+ with a surface-specific reason. `check`, `receipt` and `verify` flag omitted or unproven items; only frames the judge ties to the item
77
+ can pass. Static controls do not prove motion or timing. Capture the interaction, never substitute a mock. Without the key, approved-design behavior is unchanged;
78
+ `done-done check --help` and `docs/reference.md` explain wiring.
79
+
72
80
  To gate a worker's claim cheaply, `done-done lint <PR URL | report file> <evidence folder>` reads the newest `## Checks` block
73
81
  with no judge or model and prints `PASS` or `FAIL: <reason>` (exit 1 on a fail): missing lines, evidence that is absent,
74
82
  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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "detail-navigation": "Detail views offer next and previous navigation plus swipe between items. Demonstrate both controls and the swipe transition, not just a static detail screen.",
3
+ "instant-state": "Every send or answer immediately shows a pending, sent, answered or error state. Demonstrate the action and its immediate feedback; a final reply alone does not prove timing.",
4
+ "destructive-undo": "Destructive actions offer undo. Demonstrate the action, visible undo and restored content after undo.",
5
+ "scroll-restoration": "Lists preserve scroll position on returning from a detail view. Demonstrate the scrolled list, opening a detail and returning to the same position.",
6
+ "list-search": "Long lists have usable search or filtering. Demonstrate the long list and the changed results after searching or filtering.",
7
+ "empty-next-step": "Empty states explain the next step a user can take. Show the real empty state with its actionable explanation."
8
+ }