done-done-axi 0.4.5 → 0.4.7
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 +1 -1
- package/dist/agents.js +8 -3
- package/dist/cli.js +29 -18
- package/dist/config.js +5 -1
- package/dist/judge.js +33 -6
- package/dist/lint.js +7 -1
- package/docs/reference.md +9 -7
- package/package.json +1 -1
- package/skills/done-done/SKILL.md +4 -2
package/README.md
CHANGED
|
@@ -181,7 +181,7 @@ Run `done-done` with no arguments to see the current config and the next step.
|
|
|
181
181
|
- 👀 **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.
|
|
182
182
|
- 🎨 **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)).
|
|
183
183
|
- 📐 **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)).
|
|
184
|
-
- 🧮 **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),
|
|
184
|
+
- 🧮 **Honest, cheap claim gate**: `done-done lint <PR URL | report file> <evidence folder>` is a no-model gate on a worker's `## Checks` block, on whenever you call it. It prints `PASS` or `FAIL: <reason>` and exits 1 on a fail: the four lines present, proof naming a command or path, a real repro on a fix PR, evidence files that exist, are non-empty, are newer than the evidence folder and differ from each other, at least one never used by another PR (hashes kept in `~/.done-done/lint.hashes`, every run logged to `lint.log` there), under 1,000 added non-generated lines, and a `WARN` when a PR changes `src/` with no `journeys/` file ([how](docs/reference.md#lint-check)). `evidence: not proven: <cause>` passes.
|
|
185
185
|
- 🚦 **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)).
|
|
186
186
|
- 🧩 **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)).
|
|
187
187
|
|
package/dist/agents.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { homedir, tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, extname, isAbsolute, join } from 'node:path';
|
|
5
5
|
import { stripVTControlCharacters } from 'node:util';
|
|
6
|
-
import { AGENTS, ANSWERER_JUDGES, CHEAP, JUDGE_DEFAULT, EFFORT_FLAGS, KEY_ROUTES, parseDuration, UsageError } from './config.js';
|
|
6
|
+
import { AGENTS, ANSWERER_JUDGES, CHEAP, FALLBACK_ROUTES, JUDGE_DEFAULT, EFFORT_FLAGS, KEY_ROUTE_MODELS, KEY_ROUTES, parseDuration, UsageError } from './config.js';
|
|
7
7
|
import { isExecutable, onPath } from './judge.js';
|
|
8
8
|
// argv never carries the prompt: it goes on stdin, or (file: true) into a 0600 prompt file the argv points at.
|
|
9
9
|
// done-done parses the answer, validates it against the schema, and asks a tool-less call again, at most ASK_ATTEMPTS calls.
|
|
@@ -342,10 +342,10 @@ export function selectJudge(cfg, found) {
|
|
|
342
342
|
throw noJudge();
|
|
343
343
|
// A key route judges on the user's own key at judge.model; an agent route judges through that agent at its model.
|
|
344
344
|
const keyed = Object.hasOwn(KEY_ROUTES, route);
|
|
345
|
-
if (keyed && !cfg.judge.model)
|
|
345
|
+
if (keyed && !cfg.judge.model && !KEY_ROUTE_MODELS[route])
|
|
346
346
|
throw new UsageError(`judge: judge.route ${route} needs judge.model, a model your key can use that reads images`, 'Set judge.model in ~/.done-done/config.yaml');
|
|
347
347
|
const agent = keyed ? route : route === 'claude-code' ? 'claude' : route.slice('answerer:'.length);
|
|
348
|
-
const model = cfg.judge.model || (agent === 'pi' ? cfg.agent_config.pi?.model || JUDGE_DEFAULT : modelFor(cfg, agent));
|
|
348
|
+
const model = cfg.judge.model || KEY_ROUTE_MODELS[route] || (agent === 'pi' ? cfg.agent_config.pi?.model || JUDGE_DEFAULT : modelFor(cfg, agent));
|
|
349
349
|
if (!model)
|
|
350
350
|
throw new UsageError(`judge: no model for ${route}`, `Set judge.model in ~/.done-done/config.yaml`);
|
|
351
351
|
// acp:claude-code is claude for independence; an author with no known model is never apart by model.
|
|
@@ -354,6 +354,11 @@ export function selectJudge(cfg, found) {
|
|
|
354
354
|
: agent !== knownAgentOf(found.agent) ? 'agent' : found.model && model !== found.model ? 'model' : 'session-only';
|
|
355
355
|
return { route, agent, model, independence, author: { agent: found.agent, model: found.model } };
|
|
356
356
|
}
|
|
357
|
+
// The judge that answers when the set route's call fails (FALLBACK_ROUTES), at that agent's own default model; none for a route with no fallback.
|
|
358
|
+
export function selectFallback(cfg, found) {
|
|
359
|
+
const route = FALLBACK_ROUTES[cfg.judge.route];
|
|
360
|
+
return route ? selectJudge({ ...cfg, judge: { ...cfg.judge, route, model: '' } }, found) : undefined;
|
|
361
|
+
}
|
|
357
362
|
const DRAIN_MS = 500;
|
|
358
363
|
const OUTPUT_CAP = 64 << 20;
|
|
359
364
|
// One agent process. An edit step gets its own process group, so whatever it started (a dev server, a browser) is
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createInterface } from 'node:readline/promises';
|
|
|
9
9
|
import { parseArgs } from 'node:util';
|
|
10
10
|
import { encode } from '@toon-format/toon';
|
|
11
11
|
import { Document, parseDocument } from 'yaml';
|
|
12
|
-
import { ACPX_INSTALL, agentChoices, agentPath, agentRun, askAgent, editOnlyReason, findBin, JUDGES, modelFor, noAgent, noJudge, notStartable, routeOf, selectAgent, selectJudge, toollessReason } from './agents.js';
|
|
12
|
+
import { ACPX_INSTALL, agentChoices, agentPath, agentRun, askAgent, editOnlyReason, findBin, JUDGES, modelFor, noAgent, noJudge, notStartable, routeOf, selectAgent, selectFallback, selectJudge, toollessReason } from './agents.js';
|
|
13
13
|
import { AGENTS, CHEAP, globalConfigText, home, inputPattern, isAgent, isJudgeRoute, loadConfig, parseDuration, UsageError } from './config.js';
|
|
14
14
|
import { backend, judge, judgeable, onPath, quote, screen } from './judge.js';
|
|
15
15
|
import { startInstance, stopInstance } from './instance.js';
|
|
@@ -71,7 +71,7 @@ const DOCTOR_TEXT = `doctor changes nothing except the --probe call record (the
|
|
|
71
71
|
required: git (inside a repository), config (a valid .done-done.yaml), evidence_root (writable), agent (the agent the user
|
|
72
72
|
set and its model; missing when none is set, with the command that saves the user's choice), judge (the judge route is ready:
|
|
73
73
|
for claude-code an executable claude binary and the judge kit, for answerer:<agent> that agent's CLI and the
|
|
74
|
-
kit, for openai:key
|
|
74
|
+
kit, for a key route (openai:key, minimax:key) its API key and the kit; doctor does not call the model; missing when none is set, with the command
|
|
75
75
|
that saves the user's choice).
|
|
76
76
|
also listed: verify_skill (.agents/skills/done-done-verify-<app>/), agent_settings (agent, model, effort,
|
|
77
77
|
args, path and agent_timeout, each with the file it came from: the global config, .done-done.yaml or default),
|
|
@@ -94,7 +94,7 @@ const FLAGS = {
|
|
|
94
94
|
'--outcome "<claim>"': 'the outcome the evidence files prove; the files follow as arguments',
|
|
95
95
|
'--from <file | - | PR URL>': 'read the claim from the newest "## Checks" block of a file, stdin or a PR body',
|
|
96
96
|
'--trusted-ref <ref>': 'read .done-done.yaml from this git ref instead of the working tree (for scripts)',
|
|
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
|
|
97
|
+
'--judge <route>': 'check with this judge for this call instead of judge.route (same form: claude-code, answerer:opencode, answerer:pi, answerer:gemini, openai:key or minimax:key); its model is that agent\'s default, or judge.model when the route is the configured one',
|
|
98
98
|
'--citation-base <dir>': 'resolve evidence citations against this directory (default: cwd); absolute citations stay unchanged; does not change repo, config or run storage',
|
|
99
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
100
|
'--verify <head>': 'read-only: verify the receipt recorded for <head>',
|
|
@@ -209,7 +209,7 @@ const COMMANDS = {
|
|
|
209
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
210
|
},
|
|
211
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 or model, and two optional size rules are config keys']],
|
|
212
|
+
usage: [['done-done lint <PR URL | report file> <evidence folder>', 'merge gate on a worker\'s claim: prints PASS, WARN: <reason> or FAIL: <reason>; the rules need no judge or model, and two optional size rules are config keys']],
|
|
213
213
|
flags: [],
|
|
214
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
215
|
outcome:, repro:, proof: and evidence: lines. proof names a command or path, and the repo's done-done-verify-<app> skill, or says "no verify skill"
|
|
@@ -217,10 +217,11 @@ const COMMANDS = {
|
|
|
217
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
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
219
|
non-generated lines fails (lockfiles, vendor/, dist/, LICENSE-type files, deletions and moved lines do not count); a bot PR passes.
|
|
220
|
+
A PR URL whose diff changes a src/ file and no journeys/ file prints WARN instead of PASS (still exit 0): run the journey and commit it before the PR opens.
|
|
220
221
|
In .done-done.yaml, checks.upstream_catch_up: true also drops a fork's upstream catch-up lines, and checks.moved_from: true drops lines
|
|
221
222
|
a "moved-from: <owner>/<repo>@<ref> <dst>=<src>" body line says came from a repo of the same owner; both are off by default.
|
|
222
223
|
checks.generated_paths: ["gen/**", "*.snap"] adds your own globs to the generated files above; the default is none.
|
|
223
|
-
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`],
|
|
224
|
+
Every run is appended to lint.log next to the store. Exit 0 PASS or WARN, 1 FAIL, 2 usage error. Needs gh for a PR URL`],
|
|
224
225
|
},
|
|
225
226
|
status: {
|
|
226
227
|
usage: [['done-done status [--run <run-id>]', "show a run's steps, result, findings and next step"]],
|
|
@@ -276,8 +277,8 @@ ${GLOBAL_TEXT}
|
|
|
276
277
|
judge: done-done's own agent, a fresh one on your own plan through BYOKit, says whether each PNG/JPEG capture shows
|
|
277
278
|
the outcome; a wrong screen fails, an unsure answer is inconclusive. check, receipt, verify, rerun and design always start
|
|
278
279
|
it, and there is no way to turn it off: a pass needs a judge pass on at least one capture. The judge is the one the user set
|
|
279
|
-
(judge.route: claude-code; answerer:<agent> through the CLI of opencode, pi or gemini with every tool off; or openai:key with
|
|
280
|
-
judge.model
|
|
280
|
+
(judge.route: claude-code; answerer:<agent> through the CLI of opencode, pi or gemini with every tool off; or a key route on your own API key: openai:key with
|
|
281
|
+
judge.model, or minimax:key, which falls back to claude-code when its call fails), whichever agent and model the user picks; with none set, done-done asks in a
|
|
281
282
|
terminal or stops (exit 2) with the command that saves it. A judge that fails or does not answer stops the command with its
|
|
282
283
|
cause (exit 2), never a result.
|
|
283
284
|
|
|
@@ -445,6 +446,9 @@ function closeRun(repo, cfg, rec, log) {
|
|
|
445
446
|
return { code: reason ? FAIL : PASS, result, reason };
|
|
446
447
|
}
|
|
447
448
|
async function cmdRun(argv) {
|
|
449
|
+
const literal = argv.find((a) => /\$\{?DONE_DONE_EVIDENCE_ROOT\b/.test(a));
|
|
450
|
+
if (literal)
|
|
451
|
+
throw new UsageError(`the argument ${JSON.stringify(literal)} holds the literal text $DONE_DONE_EVIDENCE_ROOT: the shell did not expand it (single quotes?), so it would name a folder with that name. Use the env var inside the script, or double quotes`);
|
|
448
452
|
const dash = argv.indexOf('--');
|
|
449
453
|
const { values, positionals } = parseArgs({
|
|
450
454
|
args: dash < 0 ? argv : argv.slice(0, dash),
|
|
@@ -568,21 +572,25 @@ function claimFromChecks(body, title, repo, cfg, out) {
|
|
|
568
572
|
if (!/[/`]|\.\w+\b|\b(npm|pnpm|yarn|npx|node|bash|sh|python3?|cargo|go|gradle|make|adb|xcrun|done-done)\b/.test(f.proof))
|
|
569
573
|
out.push({ level: 'fail', why: 'proof names no command or path' });
|
|
570
574
|
const skill = cfg.skill.split('/').pop();
|
|
575
|
+
let ran;
|
|
571
576
|
if (existsSync(join(repo, cfg.skill))) {
|
|
572
577
|
if (!f.proof.includes(skill))
|
|
573
578
|
out.push({ level: 'fail', why: `proof does not name the project's verify skill ${skill}` });
|
|
579
|
+
else
|
|
580
|
+
ran = skill;
|
|
574
581
|
}
|
|
575
582
|
else if (!/verify-|no verify skill/i.test(f.proof))
|
|
576
583
|
out.push({ level: 'fail', why: 'proof names no verify skill and does not say "no verify skill"' });
|
|
577
584
|
if (/^n\/?a\b/i.test(f.repro) && new RegExp(cfg.checks.fix_title, 'i').test(title))
|
|
578
585
|
out.push({ level: 'fail', why: 'a fix needs a repro, not n/a' });
|
|
579
586
|
const np = f.evidence.match(/^not proven\s*:?\s*(.*)$/i);
|
|
587
|
+
ran ??= f.proof.match(/[\w.-]*verify-[\w-]+/i)?.[0];
|
|
580
588
|
if (np)
|
|
581
589
|
return { outcome: f.outcome, files: [], title, notProven: np[1] || 'no cause given' };
|
|
582
590
|
const files = f.evidence.split(/[\s,]+/).map((t) => t.replace(/^[`'"(]+|[`'"),.;]+$/g, '')).filter((t) => /\.\w{1,10}$/.test(t));
|
|
583
591
|
if (!files.length)
|
|
584
592
|
out.push({ level: 'fail', why: 'evidence names no files' });
|
|
585
|
-
return { outcome: f.outcome, files, title };
|
|
593
|
+
return { outcome: f.outcome, files, title, ...(ran && { skill: ran }) };
|
|
586
594
|
}
|
|
587
595
|
// A PR body also names its head branch, which is the claim a detached-HEAD checkout is checking.
|
|
588
596
|
function readSource(from) {
|
|
@@ -736,12 +744,14 @@ async function runCheck(argv, run) {
|
|
|
736
744
|
findings.push({ level: 'inconclusive', why: `${name}: not in any done-done run record, so it is not tied to a commit` });
|
|
737
745
|
continue;
|
|
738
746
|
}
|
|
739
|
-
//
|
|
740
|
-
|
|
747
|
+
// Identical bytes (a deterministic capture) are claimed by every run that wrote them; a run at the current commit is enough.
|
|
748
|
+
// With none, records from before runs had their own folders can both claim one file; never guess which run wrote it.
|
|
749
|
+
const atNow = claims.filter((r) => r.commit === now.commit);
|
|
750
|
+
if (!atNow.length && claims.length > 1) {
|
|
741
751
|
findings.push({ level: 'inconclusive', why: `${name}: claimed by more than one run (${claims.map((r) => r.run_id).join(', ')}), so it is not tied to one commit` });
|
|
742
752
|
continue;
|
|
743
753
|
}
|
|
744
|
-
const rec = claims
|
|
754
|
+
const rec = (atNow.length ? atNow : claims).reduce((a, b) => (b.started_at > a.started_at ? b : a));
|
|
745
755
|
// Only a capture tied to a run record can reach the judge, so a stray cited file is never sent.
|
|
746
756
|
const c = { name, bytes, sha256: hash, media: v };
|
|
747
757
|
if (v.status === 'ok' && !record && pair)
|
|
@@ -798,7 +808,8 @@ async function runCheck(argv, run) {
|
|
|
798
808
|
// No result without the judge: once the plain checks pass, with none set it asks the user, or stops with the command that saves one.
|
|
799
809
|
await withChoice('judge', cfg);
|
|
800
810
|
run?.judging(findings.length ? INCONCLUSIVE : PASS, [...findings]);
|
|
801
|
-
const
|
|
811
|
+
const author = () => (run?.author ? agentRun(cfg, run.author.agent, run.author.model) : judgeAuthor(cfg));
|
|
812
|
+
const j = await judge(cfg, () => selectJudge(cfg, author()), claim.outcome, captures, run ? join(run.dir, 'agents') : join(home(), 'agents'), () => selectFallback(cfg, author()));
|
|
802
813
|
// The judge's next step leads the help when its verdict decides the result.
|
|
803
814
|
if (j.judged.some((r) => r.verdict === 'fail'))
|
|
804
815
|
judgeLevel = 'fail';
|
|
@@ -826,7 +837,7 @@ async function runCheck(argv, run) {
|
|
|
826
837
|
mkdirSync(dirname(ledger), { recursive: true });
|
|
827
838
|
appendFileSync(ledger, hashes.filter((h) => !used.get(h)?.has(claimId)).map((h) => `${h} ${claimId}\n`).join(''));
|
|
828
839
|
}
|
|
829
|
-
return { code: rep.code, result: rep.result, judge: judged, judgeBlock, claim: claim?.outcome, files, cited, head: now, findings };
|
|
840
|
+
return { code: rep.code, result: rep.result, judge: judged, judgeBlock, claim: claim?.outcome, skill: claim?.skill, files, cited, head: now, findings };
|
|
830
841
|
}
|
|
831
842
|
async function cmdCheck(argv) {
|
|
832
843
|
const r = await runCheck(argv);
|
|
@@ -913,7 +924,7 @@ function verifyReceipt(repo, cfg, headArg) {
|
|
|
913
924
|
}
|
|
914
925
|
if (bad.length)
|
|
915
926
|
return fail(`cited evidence no longer matches the receipt: ${bad.join('; ')}`);
|
|
916
|
-
return { code: PASS, result: { command: 'receipt', result: 'pass', head: commit, tree: rec.tree, claim: rec.claim, files: rec.files.length, at: rec.at, schema: rec.schema, judge: j, help: ['This receipt is valid for this head.'] } };
|
|
927
|
+
return { code: PASS, result: { command: 'receipt', result: 'pass', head: commit, tree: rec.tree, claim: rec.claim, ...(rec.skill && { skill: rec.skill }), files: rec.files.length, at: rec.at, schema: rec.schema, judge: j, help: ['This receipt is valid for this head.'] } };
|
|
917
928
|
}
|
|
918
929
|
async function cmdReceipt(argv) {
|
|
919
930
|
const vi = argv.indexOf('--verify');
|
|
@@ -949,9 +960,9 @@ function recordReceipt(repo, cfg, r) {
|
|
|
949
960
|
// A missing citation is not a verdict: the judge passed, but the receipt cannot bind it, so it stops like a judge error.
|
|
950
961
|
if (!r.cited.length)
|
|
951
962
|
throw new UsageError('receipt: the check cited no evidence files, so no receipt was written; a receipt must bind at least one cited evidence file', 'Cite real proof: done-done receipt --outcome "<claim>" <files...>');
|
|
952
|
-
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 };
|
|
963
|
+
const rec = { schema: 2, repo: cfg.app, head: r.head.commit, tree: r.head.tree, claim: r.claim ?? '', ...(r.skill && { skill: r.skill }), result: 'pass', at: new Date().toISOString(), files: r.cited, judge: r.judgeBlock };
|
|
953
964
|
const path = writeReceipt(cfg, rec);
|
|
954
|
-
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) };
|
|
965
|
+
const result = { command: 'receipt', result: 'pass', head: r.head.commit, tree: r.head.tree, claim: rec.claim, ...(rec.skill && { skill: rec.skill }), files: rec.files.length, receipt: shown(repo, path) };
|
|
955
966
|
return { code: PASS, result: { ...result, ...(r.judge && { judge: r.judge }), help: [`Verify this receipt (merge gate): done-done receipt --verify ${r.head.commit}`] } };
|
|
956
967
|
}
|
|
957
968
|
// Why evidence from `from` no longer proves `to`, or '' if it still does.
|
|
@@ -1646,7 +1657,7 @@ async function runSteps(dir) {
|
|
|
1646
1657
|
if (p.end)
|
|
1647
1658
|
return finish(p.end.result, p.end.reason);
|
|
1648
1659
|
begin('receipt');
|
|
1649
|
-
const receipt = recordReceipt(run.repo, cfg, p.checked);
|
|
1660
|
+
const receipt = recordReceipt(run.repo, cfg, { ...p.checked, ...(run.journey === null && existsSync(join(run.repo, cfg.skill)) && { skill: basename(cfg.skill) }) });
|
|
1650
1661
|
log('receipt', receipt.result);
|
|
1651
1662
|
run.receipt = receipt.result.receipt;
|
|
1652
1663
|
set(step, receipt.code === PASS ? 'completed' : 'failed', { exit: receipt.code });
|
|
@@ -3583,7 +3594,7 @@ function cmdLint(argv) {
|
|
|
3583
3594
|
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>');
|
|
3584
3595
|
const { checks } = loadConfig(process.cwd());
|
|
3585
3596
|
const v = lint(positionals[0], positionals[1], home(), checks);
|
|
3586
|
-
console.log(v.ok ?
|
|
3597
|
+
console.log(!v.ok ? `FAIL: ${v.why}` : v.warn ? `WARN: ${v.warn}` : 'PASS');
|
|
3587
3598
|
return v.ok ? PASS : FAIL;
|
|
3588
3599
|
}
|
|
3589
3600
|
async function cmdDesign(argv) {
|
package/dist/config.js
CHANGED
|
@@ -18,7 +18,11 @@ export const AGENTS = ['claude', 'codex', 'opencode', 'pi', 'gemini'];
|
|
|
18
18
|
// claude judges through claude-code. An acp:<target> agent cannot be sent an image. codex cannot run tools-off, so it does not judge.
|
|
19
19
|
export const ANSWERER_JUDGES = ['opencode', 'pi', 'gemini'];
|
|
20
20
|
// BYOKit key routes: a judge on the user's own API key, read from this environment variable, with no agent CLI.
|
|
21
|
-
|
|
21
|
+
// openai:key needs judge.model; a route with a model here runs it when judge.model is empty (the free MiniMax route).
|
|
22
|
+
export const KEY_ROUTES = { 'openai:key': 'OPENAI_API_KEY', 'minimax:key': 'MINIMAX_API_KEY' };
|
|
23
|
+
export const KEY_ROUTE_MODELS = { 'minimax:key': 'MiniMax-M3.1-Flash-Preview' };
|
|
24
|
+
// The route that judges again when a route's own call fails, so a down or rate-limited free route never leaves a check with no verdict.
|
|
25
|
+
export const FALLBACK_ROUTES = { 'minimax:key': 'claude-code' };
|
|
22
26
|
// Or acp:<target>, any agent the user's acpx bridge can start; the target is one argv word, so it never reads as a flag.
|
|
23
27
|
const ROUTE_FORM = `claude-code (for claude), answerer:<agent> for one of ${ANSWERER_JUDGES.join(', ')}, or a key route (${Object.keys(KEY_ROUTES).join(', ')}); an acp:<target> agent cannot judge, since done-done cannot send it an image`;
|
|
24
28
|
export const isJudgeRoute = (v) => typeof v === 'string' && (v === 'claude-code' || Object.hasOwn(KEY_ROUTES, v) || ANSWERER_JUDGES.some((a) => v === `answerer:${a}`));
|
package/dist/judge.js
CHANGED
|
@@ -109,6 +109,16 @@ export function judgeEnv(cfg) {
|
|
|
109
109
|
}
|
|
110
110
|
return env;
|
|
111
111
|
}
|
|
112
|
+
// minimax:key runs the kit's Anthropic backend against MiniMax's Anthropic-compatible endpoint: the kit has no MiniMax backend, and its
|
|
113
|
+
// fetch is the host's. A request for any other address is refused, so a kit that changes its base can never send the MiniMax key elsewhere.
|
|
114
|
+
const ANTHROPIC_BASE = 'https://api.anthropic.com/v1';
|
|
115
|
+
const MINIMAX_BASE = 'https://api.minimax.io/anthropic/v1';
|
|
116
|
+
const minimaxFetch = (url, init) => {
|
|
117
|
+
const to = String(url);
|
|
118
|
+
if (!to.startsWith(`${ANTHROPIC_BASE}/`))
|
|
119
|
+
throw new UsageError(`judge: minimax:key refused a request to ${to}, which is not the Anthropic API path it redirects`, 'Update done-done, or use another judge route');
|
|
120
|
+
return fetch(MINIMAX_BASE + to.slice(ANTHROPIC_BASE.length), init);
|
|
121
|
+
};
|
|
112
122
|
// The kit loads only when a capture is judged, so a command that judges nothing stays fast.
|
|
113
123
|
async function kit() {
|
|
114
124
|
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
@@ -127,8 +137,8 @@ export async function backend(cfg, choice, record = () => '', questions = { rele
|
|
|
127
137
|
const key = process.env[name] ?? judgeEnv(cfg)[name];
|
|
128
138
|
if (!key)
|
|
129
139
|
throw new UsageError(`judge: ${choice.route} needs your own API key in ${name}, which is not set`, `Export ${name} with your key, or list it in the file judge.env_file names, then re-run this command`);
|
|
130
|
-
const { openai, resolve } = await kit();
|
|
131
|
-
const ask = openai({ model: choice.model, key, supportsImages: true, maxRetries: 0 }).ask;
|
|
140
|
+
const { anthropic, openai, resolve } = await kit();
|
|
141
|
+
const ask = (choice.route === 'minimax:key' ? anthropic({ model: choice.model, key, supportsImages: true, maxRetries: 0, maxTokens: 4096, fetch: minimaxFetch }) : openai({ model: choice.model, key, supportsImages: true, maxRetries: 0 })).ask;
|
|
132
142
|
return { ask, resolve, lastError: () => '', detail: `${name} is set and the judge kit is present` };
|
|
133
143
|
}
|
|
134
144
|
const override = cfg.agent_path_override[choice.agent];
|
|
@@ -246,10 +256,27 @@ export function writeCache(dir, key, value) {
|
|
|
246
256
|
}
|
|
247
257
|
catch { }
|
|
248
258
|
}
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
// fallback: the judge for a route that fails (selectFallback). The whole check is judged again on it, so one check never mixes routes,
|
|
260
|
+
// and the result names the route that answered; when that fails too, the error carries both causes.
|
|
261
|
+
export async function judge(cfg, choose, outcome, captures, records, fallback) {
|
|
262
|
+
try {
|
|
263
|
+
return await judgeOn(cfg, choose, outcome, captures, records);
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
const next = e instanceof UsageError && fallback?.();
|
|
267
|
+
if (!next)
|
|
268
|
+
throw e;
|
|
269
|
+
try {
|
|
270
|
+
return await judgeOn(cfg, () => next, outcome, captures, records);
|
|
271
|
+
}
|
|
272
|
+
catch (again) {
|
|
273
|
+
if (!(again instanceof UsageError))
|
|
274
|
+
throw again;
|
|
275
|
+
throw new UsageError(`${again.message} (after ${choose().route} failed: ${e.message})`, again.help);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function judgeOn(cfg, choose, outcome, captures, records) {
|
|
253
280
|
const dir = join(home(), 'runs', cfg.app, 'judge');
|
|
254
281
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
255
282
|
const judged = [];
|
package/dist/lint.js
CHANGED
|
@@ -71,9 +71,12 @@ const sha256 = (p) => createHash('sha256').update(readFileSync(p)).digest('hex')
|
|
|
71
71
|
// 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.
|
|
72
72
|
export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
73
73
|
const isGenerated = generatedBy(rules.generated_paths);
|
|
74
|
+
let warn = ''; // set for a PR that touches src/ with no journeys/ file; it only softens a PASS
|
|
74
75
|
const log = (v) => {
|
|
76
|
+
if (v.ok && warn)
|
|
77
|
+
v = { ...v, warn };
|
|
75
78
|
mkdirSync(dir, { recursive: true });
|
|
76
|
-
appendFileSync(join(dir, 'lint.log'), `${new Date().toISOString()}\t${src}\t${v.head}\t${v.ok ? 'pass' : 'fail'}\t${v.why}\n`);
|
|
79
|
+
appendFileSync(join(dir, 'lint.log'), `${new Date().toISOString()}\t${src}\t${v.head}\t${v.ok ? (v.warn ? 'warn' : 'pass') : 'fail'}\t${v.why}\n`);
|
|
77
80
|
return v;
|
|
78
81
|
};
|
|
79
82
|
let body = '', title = '', head = '-', task = '';
|
|
@@ -92,6 +95,9 @@ export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
|
92
95
|
const author = pr.author?.login ?? '';
|
|
93
96
|
if (/^(app\/)?(dependabot|renovate)(\[bot\])?$/.test(author))
|
|
94
97
|
return log({ ok: true, why: 'bot', head });
|
|
98
|
+
const paths = (pr.files ?? []).map((f) => f.path ?? '');
|
|
99
|
+
if (paths.some((p) => p.startsWith('src/')) && !paths.some((p) => p.startsWith('journeys/')))
|
|
100
|
+
warn = 'the diff changes src/ and no journeys/ file: run the journey, commit it, then open the PR';
|
|
95
101
|
let size = (pr.files ?? []).reduce((n, f) => n + (isGenerated(f.path ?? '') ? 0 : f.additions ?? 0), 0);
|
|
96
102
|
if (size >= SIZE_LIMIT) {
|
|
97
103
|
const d = run('pr', 'diff', src);
|
package/docs/reference.md
CHANGED
|
@@ -70,7 +70,7 @@ done-done init --hook pi
|
|
|
70
70
|
done-done doctor
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`doctor` answers "is this repo worth driving" and changes nothing except the `--probe` call record (the agent itself may keep its own session files). It checks nine items, each reported `ok`, `warning` or `missing` with the exact fix command (plus a `tool_gate` `warning` row when the agent is pi or codex; see [Agents](#agents)): `git` (inside a repository), `config` (a valid `.done-done.yaml`), `evidence_root` (writable), `verify_skill` (`.agents/skills/done-done-verify-<app>/SKILL.md`), `agent` (the coding agent the user set and its model; `missing` when none is set, with the command that saves the user's choice, or when it is not installed or done-done cannot start it yet; see [Agents](#agents)), `agent_settings` (each effective agent setting, `agent`, its model, effort, how many extra flags are set (never their values), binary and `agent_timeout`, and where it came from: the global config, `.done-done.yaml` or `default`), `judge` (`missing` when no judge is set, with the command that saves the user's choice; else the judge kit is present, and the route's own setup: for `claude-code` an executable `claude` binary (`agent_path_override.claude`, else PATH), with your own sign-in checked only when a capture is judged; for `answerer:<agent>` the agent on PATH; for `openai:key` the `OPENAI_API_KEY` variable and a `judge.model`; doctor does not call the model; it is checked even with no agent set), `ffprobe` (video evidence only) and `judge_choice` (the route and model the judge runs and the author it judges, see [Judge relevance](#judge-relevance): `ok`, naming how far apart they are (`agent`, `model` or `session-only`), since any judge the user sets runs; `missing`, with no fix of its own, until the agent is set; `missing` when no judge is set or the judge config is incomplete, such as a key route with no `judge.model`). `git`, `config`, `evidence_root`, `agent` and `judge` are required, since done-done gives no result without its agent and judge: `doctor` exits 0 when they are all ok and 2 otherwise. A missing `verify_skill` or `ffprobe`, or a `warning`, never fails it. In a fresh repo with nothing set up, `doctor` names each missing piece, starting with `done-done init`.
|
|
73
|
+
`doctor` answers "is this repo worth driving" and changes nothing except the `--probe` call record (the agent itself may keep its own session files). It checks nine items, each reported `ok`, `warning` or `missing` with the exact fix command (plus a `tool_gate` `warning` row when the agent is pi or codex; see [Agents](#agents)): `git` (inside a repository), `config` (a valid `.done-done.yaml`), `evidence_root` (writable), `verify_skill` (`.agents/skills/done-done-verify-<app>/SKILL.md`), `agent` (the coding agent the user set and its model; `missing` when none is set, with the command that saves the user's choice, or when it is not installed or done-done cannot start it yet; see [Agents](#agents)), `agent_settings` (each effective agent setting, `agent`, its model, effort, how many extra flags are set (never their values), binary and `agent_timeout`, and where it came from: the global config, `.done-done.yaml` or `default`), `judge` (`missing` when no judge is set, with the command that saves the user's choice; else the judge kit is present, and the route's own setup: for `claude-code` an executable `claude` binary (`agent_path_override.claude`, else PATH), with your own sign-in checked only when a capture is judged; for `answerer:<agent>` the agent on PATH; for `openai:key` the `OPENAI_API_KEY` variable and a `judge.model`; for `minimax:key` the `MINIMAX_API_KEY` variable; doctor does not call the model; it is checked even with no agent set), `ffprobe` (video evidence only) and `judge_choice` (the route and model the judge runs and the author it judges, see [Judge relevance](#judge-relevance): `ok`, naming how far apart they are (`agent`, `model` or `session-only`), since any judge the user sets runs; `missing`, with no fix of its own, until the agent is set; `missing` when no judge is set or the judge config is incomplete, such as a key route with no `judge.model`). `git`, `config`, `evidence_root`, `agent` and `judge` are required, since done-done gives no result without its agent and judge: `doctor` exits 0 when they are all ok and 2 otherwise. A missing `verify_skill` or `ffprobe`, or a `warning`, never fails it. In a fresh repo with nothing set up, `doctor` names each missing piece, starting with `done-done init`.
|
|
74
74
|
|
|
75
75
|
```sh
|
|
76
76
|
done-done doctor --probe
|
|
@@ -182,7 +182,7 @@ With your browser tool, for example chrome-devtools-axi: `overflow` and `overflo
|
|
|
182
182
|
|
|
183
183
|
### Look check
|
|
184
184
|
|
|
185
|
-
`done-done look` is the look step a journey calls on a web page. It opens the page in headless Chrome at a 390x844 phone viewport (a mobile device at 1x scale), measures the look defects in the DOM, writes the screenshot with its page-check record next to it, and exits 1 on any hit, naming the element, the kind and its text. It is a measurement step, not a verdict: with no hit it ends `result: clean` (exit 0), never `pass`, and its capture is judged when a `check` or `verify` cites it. No model runs here, so the same page gives the same answer.
|
|
185
|
+
`done-done look` is the look step a journey calls on a web page. It opens the page in headless Chrome at a 390x844 phone viewport (a mobile device at 1x scale), measures the look defects in the DOM, writes the screenshot with its page-check record next to it, and exits 1 on any hit, naming the element, the kind and its text. It is a measurement step, not a verdict: with no hit it ends `result: clean` (exit 0), never `pass`, and its capture is judged when a `check` or `verify` cites it. No model runs here, so the same page gives the same answer. A worker's claim that a phone UI works needs this 390 px capture of the real page, plus the page's scroll-container count, as proof; a mock or another width is not proof.
|
|
186
186
|
|
|
187
187
|
```sh
|
|
188
188
|
done-done look http://127.0.0.1:3000/inbox --out "$DONE_DONE_EVIDENCE_ROOT/inbox.png"
|
|
@@ -223,12 +223,13 @@ journeys:
|
|
|
223
223
|
|
|
224
224
|
### Lint check
|
|
225
225
|
|
|
226
|
-
`done-done lint <PR URL | report file> <evidence folder>` is a deterministic gate on a worker's claim: no judge, no model, and the project config only switches on two optional size rules. 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.
|
|
226
|
+
`done-done lint <PR URL | report file> <evidence folder>` is a deterministic gate on a worker's claim: no judge, no model, and the project config only switches on two optional size rules. It prints `PASS`, `WARN: <reason>` or `FAIL: <reason>` and exits 0 (PASS or WARN), 1, or 2 on a usage error. A PR URL is read with `gh pr view`; a file is read as the report.
|
|
227
227
|
|
|
228
228
|
- The newest `## Checks` block needs `outcome:`, `repro:`, `proof:` and `evidence:`.
|
|
229
229
|
- `proof:` names a command or path, and the repo's `done-done-verify-<app>` skill (a legacy `verify-<app>` skill is read too); it may say `no verify skill` only when `.agents/skills/` in the current folder holds none.
|
|
230
230
|
- `repro:` is not `n/a` when the PR title starts with `fix`, `bugfix` or `hotfix`.
|
|
231
231
|
- `evidence: not proven: <cause>` passes.
|
|
232
|
+
- A PR URL whose changed files include one under `src/` and none under `journeys/` prints `WARN` instead of `PASS` (exit 0; a `FAIL` still wins). It needs no config and never applies to a report file. The worker rule it backs: run the journey, commit it, then open the PR.
|
|
232
233
|
- 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
234
|
- 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
235
|
- Two optional size rules, both off by default and switched on per repo in `.done-done.yaml` (read from the current folder; they apply to a PR URL, never a report file). With both off a big PR is counted exactly as above. Each runs only when the PR is still at 1,000 lines, and an API failure keeps the full count.
|
|
@@ -285,7 +286,7 @@ evidence: <paths of the evidence files, or "not proven: <cause>">
|
|
|
285
286
|
|
|
286
287
|
A plain check is never a verdict on its own. A cited file that fails one of these checks, or a Checks block that breaks a form rule, stops the check before its judge is asked: it exits 2 with every cause in `error` and the next command in `help`, and gives no result. A check with no cited capture the judge reads (only video or other files, or none from a done-done run) stops the same way, before the judge is asked.
|
|
287
288
|
|
|
288
|
-
Files that are in no run record give an inconclusive result when another cited capture is one the judge reads: nothing ties them to a commit. So does a file that more than one run record claims (possible only with records from before each run had its own folder). A file whose path is named in a run record but whose bytes no longer match fails as `changed after run <run-id>` (exit 2, before the judge), not inconclusive: the path ties it to that run, so editing it after the run is tampering, not untied evidence.
|
|
289
|
+
Files that are in no run record give an inconclusive result when another cited capture is one the judge reads: nothing ties them to a commit. So does a file that more than one run record claims when none of those runs is at the current commit (possible only with records from before each run had its own folder). Byte-identical captures from several runs are fine when one of those runs is at the current commit: that run ties the file to the commit. A file whose path is named in a run record but whose bytes no longer match fails as `changed after run <run-id>` (exit 2, before the judge), not inconclusive: the path ties it to that run, so editing it after the run is tampering, not untied evidence.
|
|
289
290
|
|
|
290
291
|
`evidence: not proven: <cause>` stops the check before its judge is asked, always: exit 2, no result, no receipt. `checks.not_proven` is gone: a config that still sets it is a config error (exit 2) that says to delete the line.
|
|
291
292
|
|
|
@@ -312,8 +313,9 @@ done-done check --from pr-body.md # always judged
|
|
|
312
313
|
- `gemini`: plan mode plus a deny-all `--policy` file, which replaces your own policy folder and removes every tool, MCP ones too; the capture is an `@<file>` in the prompt, which Gemini CLI's own `@`-command code reads (outside the tool policy, per its source; not yet proven live, since Gemini CLI was not signed in on the test machine), only inside the scratch folder.
|
|
313
314
|
- An `acp:<target>` agent cannot judge, since done-done cannot send it an image; doctor's `config` row says so when `judge.route` names one.
|
|
314
315
|
- `openai:key`: a key-only route with no agent CLI. BYOKit's OpenAI backend sends the capture to the OpenAI Responses API on your own API key, which done-done reads from `OPENAI_API_KEY` and hands only to the kit (never printed or stored). Set `judge.model` to a model your key can use that reads images; there is no default. API billing applies.
|
|
316
|
+
- `minimax:key`: a key-only route on your MiniMax key, with no agent CLI. done-done reads `MINIMAX_API_KEY` and hands it only to the kit's Anthropic backend, which done-done points at MiniMax's Anthropic-compatible endpoint (`https://api.minimax.io/anthropic/v1`, the only address it will send the key to). `judge.model` is optional: empty runs `MiniMax-M3.1-Flash-Preview`, which reads images. This route is the only one with a fallback: when its call fails (a refused or missing key, a rate limit, no answer), `check`, `receipt` and `verify` judge the whole check again on `claude-code` at claude's cheap model (`agent_config.claude.model`, else the default), and the result's `judge` block names the route that answered. When the fallback fails too, the error carries both causes. Setting `judge.route: minimax:key` is your choice, like any route; `check --judge minimax:key` tries it for one call.
|
|
315
317
|
|
|
316
|
-
A provider key for an `answerer:<agent>` judge, or `OPENAI_API_KEY` for `openai:key`, is read from the environment of whoever runs done-done. To work in every shell, point `judge.env_file` (global config) at a file of `KEY=value` lines (`export` and quotes allowed); its names that the shell has not exported go to the judge's call only. done-done never writes a key into a config file and never prints one; `init --judge` on its own changes only the global config, in any directory.
|
|
318
|
+
A provider key for an `answerer:<agent>` judge, or `OPENAI_API_KEY` / `MINIMAX_API_KEY` for `openai:key` / `minimax:key`, is read from the environment of whoever runs done-done. To work in every shell, point `judge.env_file` (global config) at a file of `KEY=value` lines (`export` and quotes allowed); its names that the shell has not exported go to the judge's call only. done-done never writes a key into a config file and never prints one; `init --judge` on its own changes only the global config, in any directory.
|
|
317
319
|
|
|
318
320
|
Apart from that file, done-done never reads, copies or stores a credential. The kit is a regular dependency, installed with done-done; it needs Node 22.18 or later. The `claude-code` route uses the `claude` you already signed in with (`claude auth login`, as usual); there is no second sign-in.
|
|
319
321
|
|
|
@@ -380,7 +382,7 @@ done-done receipt --verify <head> # read-only: check a recorded receipt
|
|
|
380
382
|
|
|
381
383
|
`receipt` takes the same claim inputs as `check` and runs that check. It writes `<root>/.done-done/receipts/<head>.json` only when the check passes on the current HEAD, the tracked tree is clean, and the check cited at least one evidence file; a dirty tracked tree, a failed or inconclusive check, a pass that cited no evidence, or a moved HEAD writes nothing and exits non-zero. The record holds the repo, the head and tree sha, the claim outcome, and every cited file with its sha256 and a timestamp. Every receipt is schema 2 and holds a `judge` block: the route, model, independence and author, and each judged file's path, sha256 and verdict. A cited file is stored relative to the repo, or as `<evidence root>/<path>` when it is under an evidence root outside the repo, so the receipt still finds it from another checkout or a restored evidence root.
|
|
382
384
|
|
|
383
|
-
`receipt --verify <head>` changes nothing. The argument may be a full commit sha or a ref (`HEAD`, a branch or a tag), resolved to the commit with `git rev-parse --verify <arg>^{commit}` before the receipt lookup; an unresolvable argument fails with the same missing-receipt shape. It succeeds only when the receipt exists, is PASS, the current HEAD is still that head, and every cited file still hashes to the recorded sha256. A receipt is written only after a judge pass, so it reads schema 2 only: any other receipt (one recorded without the judge, or by an older build that let a run past its gate) fails as malformed, and a schema 2 receipt needs a well-formed `judge` block whose every verdict is `pass` for a cited file with the same sha256, which the result shows. A missing or malformed receipt and a hash mismatch are failures (exit 1), not usage errors. When the mismatched path is named in a run record, the failure names it as `changed after run <run-id>`.
|
|
385
|
+
`receipt --verify <head>` changes nothing. The argument may be a full commit sha or a ref (`HEAD`, a branch or a tag), resolved to the commit with `git rev-parse --verify <arg>^{commit}` before the receipt lookup; an unresolvable argument fails with the same missing-receipt shape. It succeeds only when the receipt exists, is PASS, the current HEAD is still that head, and every cited file still hashes to the recorded sha256. A receipt is written only after a judge pass, so it reads schema 2 only: any other receipt (one recorded without the judge, or by an older build that let a run past its gate) fails as malformed, and a schema 2 receipt needs a well-formed `judge` block whose every verdict is `pass` for a cited file with the same sha256, which the result shows. A receipt from `--from` (or a `verify` run that drove the skill) also records the verify skill that ran as `skill` (for example `done-done-verify-<app>`), which `--verify` and so the merge gate show; an older receipt without it still verifies. A missing or malformed receipt and a hash mismatch are failures (exit 1), not usage errors. When the mismatched path is named in a run record, the failure names it as `changed after run <run-id>`.
|
|
384
386
|
|
|
385
387
|
### Verify a change in one run
|
|
386
388
|
|
|
@@ -549,7 +551,7 @@ auto_fix: # per step, as no-mistakes; .done-done.yam
|
|
|
549
551
|
evidence:
|
|
550
552
|
local_root: ~/.done-done/evidence # evidence root for repos that set none (<local_root>/<app>)
|
|
551
553
|
judge: # done-done's own judge; check, receipt, verify and design always start it
|
|
552
|
-
# route: claude-code # claude-code, answerer:<agent> (opencode, pi, gemini)
|
|
554
|
+
# route: claude-code # claude-code, answerer:<agent> (opencode, pi, gemini) openai:key (reads OPENAI_API_KEY) or minimax:key (reads MINIMAX_API_KEY, claude-code fallback), set by you (`init --judge` saves the first three; a key route is set by hand) and unset until then
|
|
553
555
|
model: '' # default: that agent's agent_config model, or openai-codex/gpt-6-luna for pi
|
|
554
556
|
floor: 0.7 # every judge's pick must reach this probability to count; below it the verdict is unsure (inconclusive), never a pass
|
|
555
557
|
# env_file: ~/.config/done-done/judge.env # KEY=value lines (a provider API key) handed to the judge for names your shell has not exported; the key stays in that file
|
package/package.json
CHANGED
|
@@ -40,7 +40,8 @@ A journey that `spends` runs only after the user approves spending: `done-done r
|
|
|
40
40
|
|
|
41
41
|
For a web page, `done-done look <url>` is the look step a journey calls: it captures the page at a 390x844 phone viewport
|
|
42
42
|
with its page-check record and exits 1 on squashed, clipped or cut-off UI, naming each element. A hit fails the journey;
|
|
43
|
-
never wrap it in `|| true` or log the exit and go on. A
|
|
43
|
+
never wrap it in `|| true` or log the exit and go on. A claim that a phone UI works needs a 390 px capture of the real
|
|
44
|
+
page and the page's scroll-container count as proof: a mock, a different width or a claim with no count is not proof. A journey in `.done-done.yaml` that calls it:
|
|
44
45
|
|
|
45
46
|
```yaml
|
|
46
47
|
journeys:
|
|
@@ -51,7 +52,8 @@ journeys:
|
|
|
51
52
|
To gate a worker's claim cheaply, `done-done lint <PR URL | report file> <evidence folder>` reads the newest `## Checks` block
|
|
52
53
|
with no judge or model and prints `PASS` or `FAIL: <reason>` (exit 1 on a fail): missing lines, evidence that is absent,
|
|
53
54
|
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.
|
|
55
|
+
`evidence: not proven: <cause>` passes. A PR URL whose diff changes `src/` and no `journeys/` file prints `WARN: <reason>` (exit 0):
|
|
56
|
+
run the journey, commit it, then open the PR, in that order. Fix the cause it names and re-run; never edit the evidence to get past it.
|
|
55
57
|
|
|
56
58
|
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
59
|
receipt it wrote (`<evidence root>/.done-done/receipts/<head>.json`) into `.done-done-receipts/` in a follow-up commit. CI runs only
|