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 +7 -1
- package/dist/cli.js +30 -8
- package/dist/config.js +22 -0
- package/dist/design.js +45 -1
- package/docs/reference.md +19 -0
- package/docs/releasing.md +3 -3
- package/package.json +1 -1
- package/skills/done-done/SKILL.md +8 -0
- package/skills/done-done/interaction-checklist.json +8 -0
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`
|
|
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 (
|
|
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 =
|
|
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
|
|
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.
|
|
1
|
+
# 0.6.11 patch release
|
|
2
2
|
|
|
3
|
-
This release
|
|
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.
|
|
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
|
@@ -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
|
+
}
|