done-done-axi 0.6.11 → 0.6.12
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 +9 -6
- package/dist/cli.js +56 -22
- package/dist/config.js +32 -2
- package/dist/judge.js +7 -5
- package/docs/reference.md +34 -3
- package/docs/releasing.md +2 -2
- package/package.json +1 -1
- package/skills/done-done/SKILL.md +3 -0
package/README.md
CHANGED
|
@@ -233,7 +233,7 @@ Another fleet works the same way: add the include text to whatever your workers
|
|
|
233
233
|
- [Worked example: a CLI project](docs/example-cli.md): set up, one journey, check and receipt on a command-line tool.
|
|
234
234
|
- [Worked example: create and maintain a verify skill](docs/example-create-maintain.md): init writes the skill, a source change makes it drift, maintain fixes it, then a real browser journey is judged.
|
|
235
235
|
- [Full reference](docs/reference.md): every command and option, the Checks block rules, each file check, and both config files.
|
|
236
|
-
- [Config reference](docs/reference.md#config): optional global `judge.account_command` selects a pooled Claude account for judge and agent calls; an exported `CLAUDE_CONFIG_DIR` wins
|
|
236
|
+
- [Config reference](docs/reference.md#config): optional global `judge.account_command` selects a pooled Claude account for judge and agent calls; an exported `CLAUDE_CONFIG_DIR` wins; optional `judge.fallback_route: answerer:pi` with `agent_config.pi.model: openai-codex/gpt-6.1-sol` uses Codex when Claude has no eligible account, sign-in or usage left.
|
|
237
237
|
- [Report a problem or ask a question](https://github.com/umeranjum17/done-done-axi/issues)
|
|
238
238
|
|
|
239
239
|
## What's next
|
package/dist/agents.js
CHANGED
|
@@ -397,10 +397,10 @@ export function selectJudge(cfg, found) {
|
|
|
397
397
|
: agent !== knownAgentOf(found.agent) ? 'agent' : found.model && model !== found.model ? 'model' : 'session-only';
|
|
398
398
|
return { route, agent, model, independence, author: { agent: found.agent, model: found.model } };
|
|
399
399
|
}
|
|
400
|
-
//
|
|
400
|
+
// One configured second route, else the legacy MiniMax fallback; use its own model, never the primary's.
|
|
401
401
|
export function selectFallback(cfg, found) {
|
|
402
|
-
const route = FALLBACK_ROUTES[cfg.judge.route];
|
|
403
|
-
return route ? selectJudge({ ...cfg, judge: { ...cfg.judge, route, model: '' } }, found) : undefined;
|
|
402
|
+
const route = cfg.judge.fallback_route || FALLBACK_ROUTES[cfg.judge.route];
|
|
403
|
+
return route && route !== cfg.judge.route ? selectJudge({ ...cfg, judge: { ...cfg.judge, route, model: '' } }, found) : undefined;
|
|
404
404
|
}
|
|
405
405
|
const DRAIN_MS = 500;
|
|
406
406
|
const OUTPUT_CAP = 64 << 20;
|
|
@@ -510,13 +510,16 @@ function start(bin, argv, c, cwd, input, env, idleMs) {
|
|
|
510
510
|
child.stdin.end(input);
|
|
511
511
|
});
|
|
512
512
|
}
|
|
513
|
+
// A valid picker refusal is availability, not a malformed account configuration.
|
|
514
|
+
export class ClaudeUnavailable extends UsageError {
|
|
515
|
+
}
|
|
513
516
|
let accountPick;
|
|
514
517
|
// One pick (including failure) per process, shared by readers, drives and the judge; never change the caller's env.
|
|
515
518
|
export function claudeAccount(agent, command) {
|
|
516
519
|
if (agent !== 'claude' || !command || process.env.CLAUDE_CONFIG_DIR !== undefined)
|
|
517
520
|
return Promise.resolve(undefined);
|
|
518
521
|
return accountPick ??= Promise.resolve().then(() => {
|
|
519
|
-
const fail = (why) => { throw new UsageError(`judge.account_command: ${why}`, 'Fix judge.account_command in the global config, or export CLAUDE_CONFIG_DIR for this call'); };
|
|
522
|
+
const fail = (why, unavailable = false) => { throw new (unavailable ? ClaudeUnavailable : UsageError)(`judge.account_command: ${why}`, 'Fix judge.account_command in the global config, or export CLAUDE_CONFIG_DIR for this call'); };
|
|
520
523
|
const r = spawnSync(command, { shell: true, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 });
|
|
521
524
|
if (r.error)
|
|
522
525
|
fail(r.error.code ?? 'command could not start');
|
|
@@ -528,9 +531,9 @@ export function claudeAccount(agent, command) {
|
|
|
528
531
|
fail(r.status === 0 ? 'invalid JSON' : `command exited ${r.status}: ${errorLine(r.stderr) || 'no pick returned'}`);
|
|
529
532
|
}
|
|
530
533
|
if (r.status !== 0)
|
|
531
|
-
fail(`command exited ${r.status}: ${typeof pick?.reason === 'string' ? errorLine(pick.reason) : errorLine(r.stderr) || 'no pick returned'}
|
|
534
|
+
fail(`command exited ${r.status}: ${typeof pick?.reason === 'string' ? errorLine(pick.reason) : errorLine(r.stderr) || 'no pick returned'}`, pick?.ok === false || pick === null);
|
|
532
535
|
if (pick?.ok !== true)
|
|
533
|
-
fail(typeof pick?.reason === 'string' ? errorLine(pick.reason) : 'ok must be true');
|
|
536
|
+
fail(typeof pick?.reason === 'string' ? errorLine(pick.reason) : 'ok must be true', pick?.ok === false || pick === null);
|
|
534
537
|
if (typeof pick.id !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(pick.id))
|
|
535
538
|
fail('invalid account id');
|
|
536
539
|
const folder = pick.set?.CLAUDE_CONFIG_DIR;
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,8 @@ const JOURNEYS_TEXT = `journeys (.done-done.yaml): the command runs with DONE_DO
|
|
|
47
47
|
spends (journeys.<name>.spends): what a run spends, e.g. "one live generation"; verify parks the journey until the user approves that run (respond --action approve)
|
|
48
48
|
timeout (journeys.<name>.timeout): how long a verify run may drive the journey, e.g. 90s or 30m (default 30m); past it the journey is killed and the run is inconclusive
|
|
49
49
|
matrix (.done-done.yaml): with theme, width or form axes the journey runs once per required cell with DONE_DONE_THEME, DONE_DONE_WIDTH, DONE_DONE_FORM and DONE_DONE_CELL (the capture name suffix) set; name each capture with its suffix, so every cell is captured or skipped with a reason`;
|
|
50
|
+
// A claim about how the product looks or what it outputs needs a comparison with the approved result.
|
|
51
|
+
const DESIGN_CLAIM = /\b(design(ed|s)?|redesign(ed)?|mock(up)?s?|prototype|approved)\b/i;
|
|
50
52
|
const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then these four keys, each at the start of a line:
|
|
51
53
|
outcome: <what a user can now do>
|
|
52
54
|
repro: <command that failed before and passes now, or n/a; a fix PR needs one>
|
|
@@ -54,6 +56,8 @@ const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then
|
|
|
54
56
|
evidence: <the evidence file paths, or "not proven: <cause>">
|
|
55
57
|
approved_design (.done-done.yaml): a non-empty manifest of {screen, size: <width>x<height>, theme, mock: <repo PNG/JPEG>, capture: <unique filename>};
|
|
56
58
|
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
|
|
59
|
+
approved_requirements (.done-done.yaml): approved assets, color tokens and interactions as {kind: asset|color|interaction, name, capture, mock?}; skip one only with {kind, name, exclusion: <reason>}
|
|
60
|
+
(reference images under design/, mocks/, approved/ or prototype/ must be listed or excluded; a design claim with no approved reference or base_ref baseline fails as not compared to approved result)
|
|
57
61
|
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
62
|
or {not_applicable: <reason>}; detail-navigation, instant-state, destructive-undo, scroll-restoration, list-search, empty-next-step.
|
|
59
63
|
check/receipt/verify report every item: pass only with judged real-product frames, not_applicable with a reason, otherwise flagged (exit 1).
|
|
@@ -113,7 +117,9 @@ const CONFIG_TEXT = `config: ~/.done-done/config.yaml (global; DONE_DONE_HOME mo
|
|
|
113
117
|
agent (either file): claude, codex, opencode, pi, gemini or acp:<target>, set by the user (done-done init --agent); done-done never picks one.
|
|
114
118
|
global only: agent_config.<agent>.{model,effort} (effort: minimal, low, medium, high, xhigh, max), agent_args_override.<agent>
|
|
115
119
|
(extra flags), agent_path_override.<agent> (binary), acpx_path, agent_timeout (one agent call, default 30m; a streaming agent that prints nothing for 5m is stopped early),
|
|
116
|
-
judge.account_command (optional
|
|
120
|
+
judge.account_command (optional Claude account pick; exported CLAUDE_CONFIG_DIR wins),
|
|
121
|
+
judge.fallback_route (optional second judge, same values as judge.route; Claude usage/rate/sign-in limits or an empty pool trigger it;
|
|
122
|
+
e.g. answerer:pi with agent_config.pi.model: openai-codex/gpt-6.1-sol; unset keeps Claude failures as errors; no fallback chains),
|
|
117
123
|
evidence.local_root, judge.{route,model,floor} (the judge and shared confidence floor, set by the user with
|
|
118
124
|
init --judge; never picked). done-done doctor shows where each agent setting came from.`;
|
|
119
125
|
// One table drives `--help` and every `<command> --help`: each command's usage lines and the flags it takes.
|
|
@@ -909,6 +915,20 @@ async function runCheck(argv, run) {
|
|
|
909
915
|
else if (`${live[0].media.w}x${live[0].media.h}` !== e.size)
|
|
910
916
|
coverageFaults.push(`${label}: live capture is ${live[0].media.w}x${live[0].media.h}, not the approved size`);
|
|
911
917
|
}
|
|
918
|
+
// Approved assets, color tokens and interactions: each needs its own live capture, or an explicit exclusion with a reason.
|
|
919
|
+
const requirements = cfg.approved_requirements.filter(r => r.capture).map(r => ({ r, live: captures.filter(c => screen(c) && basename(cite(c.name)) === r.capture) }));
|
|
920
|
+
for (const { r, live } of requirements)
|
|
921
|
+
if (live.length !== 1)
|
|
922
|
+
coverageFaults.push(`${r.kind} ${r.name}: ${live.length ? 'ambiguous live captures' : 'no live capture'} (${r.capture})`);
|
|
923
|
+
// Committed approved reference images (design/, mocks/ ...) that no manifest row or exclusion accounts for.
|
|
924
|
+
const accounted = new Set([...cfg.approved_design.map(e => e.mock), ...cfg.approved_requirements.flatMap(r => [r.mock, r.kind === 'reference' ? r.name : undefined])]);
|
|
925
|
+
const unlisted = claim ? git(repo, 'ls-files', '-z', '--', ':(icase)design*/**', ':(icase)mock*/**', ':(icase)approved*/**', ':(icase)prototype*/**').split('\0').filter(f => /\.(png|jpe?g)$/i.test(f) && !accounted.has(f)) : [];
|
|
926
|
+
if (unlisted.length)
|
|
927
|
+
coverageFaults.push(`approved reference material is not in the approved_design manifest: ${unlisted.join(', ')}; list each as an approved_design screen, an approved_requirements mock, or {kind: reference, name: <path>, exclusion: <reason>}`);
|
|
928
|
+
// A design or output claim must be compared to the approved result (a manifest mock, or the base_ref baseline), not only rendered.
|
|
929
|
+
const compared = approved.length > 0 || requirements.some(({ r }) => r.mock) || heads.some(([c]) => c.before);
|
|
930
|
+
if (claim && !compared && DESIGN_CLAIM.test(claim.outcome))
|
|
931
|
+
coverageFaults.push('not compared to approved result: the claim is about design or output but nothing is compared to an approved reference or a same-model baseline; add an approved_design manifest (or an approved_requirements row with a mock) or set base_ref, because render, fit and dark-mode checks alone do not prove it');
|
|
912
932
|
for (const why of coverageFaults)
|
|
913
933
|
findings.push({ level: 'fail', why: `approved design: ${why}` });
|
|
914
934
|
// A head capture of a base_ref run with no base capture of its name is reported, then judged alone.
|
|
@@ -993,28 +1013,29 @@ async function runCheck(argv, run) {
|
|
|
993
1013
|
if (j.notJudged.length)
|
|
994
1014
|
judged.not_judged = j.notJudged;
|
|
995
1015
|
judged.note = 'PNG and JPEG screens and text captures (logs, JSON, CLI output) are judged; video and other files are not';
|
|
1016
|
+
const pairUp = (label, mockFile, live, size) => {
|
|
1017
|
+
const path = resolve(repo, mockFile);
|
|
1018
|
+
let committed;
|
|
1019
|
+
try {
|
|
1020
|
+
committed = execFileSync('git', ['show', `${values['trusted-ref'] ?? now.commit}:${relative(repo, path)}`], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1021
|
+
}
|
|
1022
|
+
catch {
|
|
1023
|
+
throw new UsageError(`approved design: ${label}: approved mock is not committed: ${mockFile}`);
|
|
1024
|
+
}
|
|
1025
|
+
if (under(repo, realpathSync(path)) === null)
|
|
1026
|
+
throw new UsageError(`approved design: mock leaves the repo: ${mockFile}`);
|
|
1027
|
+
const mock = loadScreen(path);
|
|
1028
|
+
if (mock.sha256 !== sha256(committed))
|
|
1029
|
+
throw new UsageError(`approved design: approved mock differs from the committed reference: ${mockFile}`);
|
|
1030
|
+
if (size && `${mock.media.w}x${mock.media.h}` !== size)
|
|
1031
|
+
throw new UsageError(`approved design: ${label}: mock has the wrong pixel size`);
|
|
1032
|
+
return { ...live, mock };
|
|
1033
|
+
};
|
|
1034
|
+
const agentRecords = run ? join(run.dir, 'agents') : join(home(), 'agents');
|
|
996
1035
|
let matches = [];
|
|
997
1036
|
if (approved.length) {
|
|
998
|
-
const pairs = approved.map(({ entry, live }) => {
|
|
999
|
-
|
|
1000
|
-
let committed;
|
|
1001
|
-
try {
|
|
1002
|
-
committed = execFileSync('git', ['show', `${values['trusted-ref'] ?? now.commit}:${relative(repo, path)}`], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1003
|
-
}
|
|
1004
|
-
catch {
|
|
1005
|
-
throw new UsageError(`approved design: ${entry.screen} / ${entry.size} / ${entry.theme}: approved mock is not committed: ${entry.mock}`);
|
|
1006
|
-
}
|
|
1007
|
-
if (under(repo, realpathSync(path)) === null)
|
|
1008
|
-
throw new UsageError(`approved design: mock leaves the repo: ${entry.mock}`);
|
|
1009
|
-
const mock = loadScreen(path);
|
|
1010
|
-
if (mock.sha256 !== sha256(committed))
|
|
1011
|
-
throw new UsageError(`approved design: approved mock differs from the committed reference: ${entry.mock}`);
|
|
1012
|
-
return { ...live[0], mock };
|
|
1013
|
-
});
|
|
1014
|
-
for (const [i, p] of pairs.entries())
|
|
1015
|
-
if (`${p.mock.media.w}x${p.mock.media.h}` !== approved[i].entry.size)
|
|
1016
|
-
throw new UsageError(`approved design: ${approved[i].entry.screen} / ${approved[i].entry.size} / ${approved[i].entry.theme}: mock has the wrong pixel size`);
|
|
1017
|
-
const verdicts = await design(cfg, choice, pairs, run ? join(run.dir, 'agents') : join(home(), 'agents'));
|
|
1037
|
+
const pairs = approved.map(({ entry, live }) => pairUp(`${entry.screen} / ${entry.size} / ${entry.theme}`, entry.mock, live[0], entry.size));
|
|
1038
|
+
const verdicts = await design(cfg, choice, pairs, agentRecords);
|
|
1018
1039
|
matches = verdicts.map((v, i) => {
|
|
1019
1040
|
const { screen, size, theme } = approved[i].entry;
|
|
1020
1041
|
if (v.verdict !== 'pass')
|
|
@@ -1028,7 +1049,17 @@ async function runCheck(argv, run) {
|
|
|
1028
1049
|
if (judgeBlock)
|
|
1029
1050
|
judgeBlock.approved_design = matches;
|
|
1030
1051
|
}
|
|
1031
|
-
const
|
|
1052
|
+
const withMock = requirements.filter(({ r }) => r.mock);
|
|
1053
|
+
if (withMock.length) {
|
|
1054
|
+
const pairs = withMock.map(({ r, live }) => pairUp(`${r.kind} ${r.name}`, r.mock, live[0]));
|
|
1055
|
+
const verdicts = await design(cfg, choice, pairs, agentRecords);
|
|
1056
|
+
for (const [i, v] of verdicts.entries()) {
|
|
1057
|
+
const { r } = withMock[i];
|
|
1058
|
+
if (v.verdict !== 'pass')
|
|
1059
|
+
findings.push({ level: 'fail', why: `approved design: ${r.kind} ${r.name}: ${v.verdict === 'unsure' ? 'match not proven: ' + v.reason : v.defects.map(d => `${d.kind}: ${d.where}`).join('; ')}`, file: v.path });
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const touches = await interactions(cfg, choice, captures, agentRecords);
|
|
1032
1063
|
for (const r of touches) {
|
|
1033
1064
|
r.captures = r.captures.map(c => ({ ...c, path: receiptPath(repo, cfg, cite(c.path)) }));
|
|
1034
1065
|
if (r.status === 'flagged')
|
|
@@ -1041,6 +1072,9 @@ async function runCheck(argv, run) {
|
|
|
1041
1072
|
rep.result.interaction_checklist = touches;
|
|
1042
1073
|
if (matches.length)
|
|
1043
1074
|
rep.result.design = matches;
|
|
1075
|
+
const excluded = cfg.approved_requirements.filter(r => r.exclusion).map(({ kind, name, exclusion }) => ({ kind, name, exclusion }));
|
|
1076
|
+
if (excluded.length)
|
|
1077
|
+
rep.result.approved_exclusions = excluded;
|
|
1044
1078
|
if (rep.code === PASS) {
|
|
1045
1079
|
mkdirSync(dirname(ledger), { recursive: true });
|
|
1046
1080
|
appendFileSync(ledger, hashes.filter((h) => !used.get(h)?.has(claimId)).map((h) => `${h} ${claimId}\n`).join(''));
|
package/dist/config.js
CHANGED
|
@@ -13,10 +13,11 @@ export class UsageError extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
export const INTERACTIONS = JSON.parse(readFileSync(new URL('../skills/done-done/interaction-checklist.json', import.meta.url), 'utf8'));
|
|
16
|
+
export const REQUIREMENT_KINDS = ['asset', 'color', 'interaction', 'reference'];
|
|
16
17
|
// The coding agents a user can set as agent.
|
|
17
18
|
export const AGENTS = ['claude', 'codex', 'opencode', 'pi', 'gemini'];
|
|
18
19
|
// The agents whose CLI can read an attached image with every tool off, so they judge through the answerer route;
|
|
19
|
-
// claude judges through claude-code. An acp:<target> agent cannot be sent an image. Codex image judging is not implemented
|
|
20
|
+
// claude judges through claude-code. An acp:<target> agent cannot be sent an image. Native Codex image judging is not implemented; use answerer:pi with openai-codex/<model>.
|
|
20
21
|
export const ANSWERER_JUDGES = ['opencode', 'pi', 'gemini'];
|
|
21
22
|
// BYOKit key routes: a judge on the user's own API key, read from this environment variable, with no agent CLI.
|
|
22
23
|
// openai:key needs judge.model; a route with a model here runs it when judge.model is empty (the free MiniMax route).
|
|
@@ -69,6 +70,7 @@ const KEYS = {
|
|
|
69
70
|
dom: { scope: 'repo', type: 'dom' },
|
|
70
71
|
look: { scope: 'repo', type: 'look' },
|
|
71
72
|
approved_design: { scope: 'repo', type: 'approved_design' },
|
|
73
|
+
approved_requirements: { scope: 'repo', type: 'approved_requirements' },
|
|
72
74
|
interaction_checklist: { scope: 'repo', type: 'interaction_checklist' },
|
|
73
75
|
base_ref: { scope: 'repo', type: 'string' },
|
|
74
76
|
'evidence.root': { scope: 'repo', type: 'string' },
|
|
@@ -87,6 +89,7 @@ const KEYS = {
|
|
|
87
89
|
'judge.floor': { scope: 'global', type: 'number' },
|
|
88
90
|
'judge.env_file': { scope: 'global', type: 'string' },
|
|
89
91
|
'judge.account_command': { scope: 'global', type: 'string' },
|
|
92
|
+
'judge.fallback_route': { scope: 'global', type: 'route' },
|
|
90
93
|
};
|
|
91
94
|
const SECTIONS = new Set(['evidence', 'secrets', 'checks', 'judge', 'signoff']);
|
|
92
95
|
const MAPS = new Set(['auto_fix', 'agent_config', 'agent_args_override', 'agent_path_override']);
|
|
@@ -235,6 +238,30 @@ function apply(target, text, file, scope) {
|
|
|
235
238
|
target.from[key] = scope;
|
|
236
239
|
continue;
|
|
237
240
|
}
|
|
241
|
+
if (key === 'approved_requirements') {
|
|
242
|
+
const fail = () => { throw new UsageError(`${file}: approved_requirements must be a non-empty list of unique {kind: ${REQUIREMENT_KINDS.join('|')}, name, capture: <live PNG/JPEG filename>, mock?: <repo-relative PNG/JPEG>} or {kind, name, exclusion: <reason>}; a reference row names a committed approved image and takes only an exclusion`); };
|
|
243
|
+
const image = (p) => typeof p === 'string' && !isAbsolute(p) && !p.split(/[\\/]/).includes('..') && /\.(png|jpe?g)$/i.test(p);
|
|
244
|
+
if (!Array.isArray(value) || !value.length)
|
|
245
|
+
fail();
|
|
246
|
+
const rows = value;
|
|
247
|
+
const ids = new Set(), names = new Set();
|
|
248
|
+
for (const r of rows) {
|
|
249
|
+
if (!isMap(r) || !REQUIREMENT_KINDS.includes(r.kind) || typeof r.name !== 'string' || !r.name.trim())
|
|
250
|
+
fail();
|
|
251
|
+
const keys = Object.keys(r).filter(k => k !== 'kind' && k !== 'name').sort().join();
|
|
252
|
+
if (typeof r.exclusion === 'string' ? !r.exclusion.trim() || keys !== 'exclusion' : r.kind === 'reference' || !['capture', 'capture,mock'].includes(keys) || !image(r.capture) || /[\\/]/.test(r.capture) || (r.mock !== undefined && !image(r.mock)))
|
|
253
|
+
fail();
|
|
254
|
+
const id = JSON.stringify([r.kind, r.name]);
|
|
255
|
+
if (ids.has(id) || (r.capture && names.has(r.capture)))
|
|
256
|
+
fail();
|
|
257
|
+
ids.add(id);
|
|
258
|
+
if (r.capture)
|
|
259
|
+
names.add(r.capture);
|
|
260
|
+
}
|
|
261
|
+
target.approved_requirements = rows;
|
|
262
|
+
target.from[key] = scope;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
238
265
|
if (key === 'approved_design') {
|
|
239
266
|
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>}`); };
|
|
240
267
|
const relativeImage = (p) => typeof p === 'string' && !isAbsolute(p) && !p.split(/[\\/]/).includes('..') && /\.(png|jpe?g)$/i.test(p);
|
|
@@ -334,6 +361,7 @@ function defaultConfig(repo) {
|
|
|
334
361
|
matrix: null,
|
|
335
362
|
dom: null,
|
|
336
363
|
look: null,
|
|
364
|
+
approved_requirements: [],
|
|
337
365
|
approved_design: [],
|
|
338
366
|
interaction_checklist: null,
|
|
339
367
|
base_ref: '',
|
|
@@ -341,7 +369,7 @@ function defaultConfig(repo) {
|
|
|
341
369
|
secrets: { patterns: {}, allow: [] },
|
|
342
370
|
checks: { fix_title: '^(fix|bugfix|hotfix)\\b', upstream_catch_up: false, moved_from: false, generated_paths: [] },
|
|
343
371
|
signoff: { post: false, board: '' },
|
|
344
|
-
judge: { route: '', model: '', floor: 0.7, env_file: '', account_command: '' },
|
|
372
|
+
judge: { route: '', fallback_route: '', model: '', floor: 0.7, env_file: '', account_command: '' },
|
|
345
373
|
trusted: false,
|
|
346
374
|
from: {},
|
|
347
375
|
};
|
|
@@ -421,6 +449,8 @@ evidence:
|
|
|
421
449
|
judge:
|
|
422
450
|
# Which agent judges: claude-code (claude) or answerer:opencode. Any agent and model you choose, the author's own too.
|
|
423
451
|
# route: answerer:opencode
|
|
452
|
+
# Optional second judge when Claude has no eligible account, sign-in or usage left; its agent_config model is used.
|
|
453
|
+
# fallback_route: answerer:pi # agent_config.pi.model: openai-codex/gpt-6.1-sol
|
|
424
454
|
# The judge's model; empty means that agent's agent_config model, or Luna for pi.
|
|
425
455
|
model: ""
|
|
426
456
|
# Every judge's pick must reach this probability to count: below it the verdict is unsure (inconclusive), never a pass.
|
package/dist/judge.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { accessSync, constants, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { basename, delimiter, isAbsolute, join } from 'node:path';
|
|
5
|
-
import { agentRun, askAgent, claudeAccount, notStartable } from './agents.js';
|
|
5
|
+
import { agentRun, askAgent, claudeAccount, ClaudeUnavailable, notStartable } from './agents.js';
|
|
6
6
|
import { home, KEY_ROUTES, parseDuration, tilde, UsageError } from './config.js';
|
|
7
7
|
import { downscaleToPng } from './media.js';
|
|
8
8
|
const MAX_SIDE = 1280; // long edge sent to the model, in pixels
|
|
@@ -225,10 +225,11 @@ export async function askJudge(cfg, be, choice, state, questions, images, shared
|
|
|
225
225
|
catch (e) {
|
|
226
226
|
const why = be.lastError() || e.message;
|
|
227
227
|
if (choice.route === 'claude-code' && /not logged in/i.test(why))
|
|
228
|
-
throw new
|
|
228
|
+
throw new ClaudeUnavailable(`judge: claude is not signed in (${why})`, 'sign in with: claude auth login, then re-run this command');
|
|
229
229
|
const freeTier = choice.agent === 'opencode' && /free tier can only be used from within OpenCode/.test(why);
|
|
230
230
|
const hint = choice.route === 'claude-code' ? ` (check judge.model ${choice.model} and your plan's usage limit)` : Object.hasOwn(KEY_ROUTES, choice.route) ? ` (check judge.model ${choice.model} and the key in ${KEY_ROUTES[choice.route]})` : freeTier ? ` (${choice.model} is on OpenCode's free tier, which only runs inside OpenCode, and the judge runs with every tool off: set judge.model in ~/.done-done/config.yaml to a model that is not on the free tier)` : '';
|
|
231
|
-
|
|
231
|
+
const Failure = choice.route === 'claude-code' && /rate.?limit|usage.?limit|not signed in|(?:every|all|no eligible).*account.*(?:floor|limit|available)/i.test(why) ? ClaudeUnavailable : UsageError;
|
|
232
|
+
throw new Failure(`judge: the judge did not answer: ${why}${hint}`, again);
|
|
232
233
|
}
|
|
233
234
|
const bad = malformed(answers);
|
|
234
235
|
if (bad.length)
|
|
@@ -274,11 +275,12 @@ export async function judge(cfg, choose, outcome, captures, records, fallback) {
|
|
|
274
275
|
return await judgeOn(cfg, choose, outcome, captures, records);
|
|
275
276
|
}
|
|
276
277
|
catch (e) {
|
|
277
|
-
const next = e instanceof UsageError && fallback?.();
|
|
278
|
+
const next = e instanceof UsageError && (choose().route !== 'claude-code' || e instanceof ClaudeUnavailable) && fallback?.();
|
|
278
279
|
if (!next)
|
|
279
280
|
throw e;
|
|
280
281
|
try {
|
|
281
|
-
|
|
282
|
+
const result = await judgeOn(cfg, () => next, outcome, captures, records);
|
|
283
|
+
return { ...result, detail: `${next.route} answered after ${choose().route} unavailable${result.detail ? `; ${result.detail}` : ''}` };
|
|
282
284
|
}
|
|
283
285
|
catch (again) {
|
|
284
286
|
if (!(again instanceof UsageError))
|
package/docs/reference.md
CHANGED
|
@@ -98,7 +98,7 @@ done-done doctor --probe
|
|
|
98
98
|
|
|
99
99
|
done-done starts the coding agent you choose, and never picks one. `agent:` names exactly one (`claude`, `codex`, `opencode`, `pi`, `gemini` or `acp:<target>`); there is no auto-detection and no fallback list, so an agent that is not installed or cannot start stops the command with the reason. While no agent is set, a command that starts it asks in a terminal (a numbered list of agents, then of models) and saves the answer to the global config; anywhere else (an agent calling done-done) it exits 2 with `no agent is set: ask the user which agent to use (claude, codex, opencode, pi, gemini, acp:<target>); never pick one for them` and the command that saves the answer: `done-done init --agent <agent> --model <model>`. `doctor` reports an unset agent as missing with that same fix. The model is `agent_config.<agent>.model` (global config only), else the agent's cheap default (`claude-haiku-5-5` for claude, `opencode-go/muse-spark-1.3-contributor` for opencode, `flash-lite` for gemini; claude and opencode also default to `effort: low`). That table is the only place done-done names a default model, and `init` writes it into the global config. `agent_config.<agent>.effort` (`minimal`, `low`, `medium`, `high`, `xhigh` or `max`, or `''` for the CLI's own) goes to the CLI's own flag as written: `--effort` (claude), `-c model_reasoning_effort="<level>"` (codex), `--variant` (opencode) and `--thinking` (pi); gemini and `acp:<target>` have none, so an effort for them is a config error. `agent_args_override.<agent>` adds extra CLI flags after done-done's own (do not repeat a flag done-done passes), `agent_path_override.<agent>` starts that binary instead of the one on PATH, `acpx_path` names the acpx bridge, and `agent_timeout` (default `30m`) bounds each agent call: a verify drive, a maintain reader, the judge and the doctor probe. A streaming agent (pi, opencode, codex, `acp:<target>`) that prints nothing, on stdout or stderr, for 5 minutes is stopped early (30 seconds for the doctor probe); the no-answer reason then quotes its stderr and the call record path, so a hung agent costs minutes, not the whole timeout. A rate limit the CLI prints (HTTP 429, a usage limit) ends any agent call at that line, with the limit and its reset time as the reason (for pi's JSON events, the provider's own message, not the event); the one exception is a claude judge call with images, which stops only at claude's own rejected rate-limit event, so its retries run. claude and gemini print one answer at the end, so only `agent_timeout` bounds them. These defaults are the cost guard for those agents: done-done never defaults to a flagship model for them. codex, pi and `acp:<target>` have no public cheap alias, so with no `agent_config.<agent>.model` (for `acp:<target>`, `agent_config.acp:<target>.model`) they run their own configured model, which can be a flagship; that setting caps their cost. `agent: acp:<target>` (for example `acp:gemini`) starts any Agent Client Protocol agent through [acpx](https://github.com/openclaw/acpx), which you install yourself (`npm install -g acpx`). doctor reports an `acp:` agent missing, with that install step, when acpx is not on PATH or does not start.
|
|
100
100
|
|
|
101
|
-
Each call is a fresh, single-shot session that must answer one JSON object matching a schema. The prompt goes on stdin (claude, codex, pi, gemini, acp) or into a 0600 prompt file (opencode), never in argv. done-done can start claude (`claude -p --output-format json --json-schema <schema> --model <model> --no-session-persistence`, all tools off with `--tools ''`; tool-less calls also pass `--setting-sources ''` and run from a scratch folder, so no project settings or hooks load; read-only calls allow only `--tools Read,Grep,Glob` and pass `--setting-sources ''` and `--strict-mcp-config`, so no user settings or MCP servers load), opencode (`opencode run "Follow the attached instructions." --format json -m <provider/model> -f <prompt file>`, with the schema in the 0600 prompt file and every tool denied through `OPENCODE_PERMISSION` except on an edit step ([Drive by agent](#drive-by-agent), Scope)), pi (`pi -p --mode json --no-session --no-tools`, tools off for every read or probe call, since pi has no read-only mode that refuses outside reads, and `--model <model>` only when one is set) and gemini (`gemini -p "Follow the instructions on stdin." -o json -m <model> --approval-mode plan|auto_edit`, where `plan` is read-only for the probe and read steps and `auto_edit` is used for edit steps; `--skip-trust` is added only to the tool-less probe in its scratch folder, so repo calls keep Gemini's workspace trust gate) and codex (`codex exec --json --ephemeral [-m <model>]`, the answer being the last agent message of its JSONL events; an edit step runs in codex's `--sandbox workspace-write` with the run's evidence and scratch folders added by `--add-dir` and network on, so the drive can launch the app and talk to it, while it keeps your own codex config). Codex readers and probes run a separate `--no-daemon exec --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only` call, with project instructions disabled and your normal authentication untouched. Feature flags alone do not remove all tools: model metadata can force code-mode, collaboration and patch tools back on. done-done reads the CLI's public bundled catalog (`codex debug models --bundled`), writes a scratch-only copy with those capabilities removed, disables the remaining tool features and web search, and supplies it through `model_catalog_json`. A missing catalog, unsupported CLI or model not in that catalog fails with its cause; no unsafe fallback or copied login is used. `doctor --probe` now requires a real Codex answer, and `init` and maintain use the same clean reader. Codex image judging is not implemented
|
|
101
|
+
Each call is a fresh, single-shot session that must answer one JSON object matching a schema. The prompt goes on stdin (claude, codex, pi, gemini, acp) or into a 0600 prompt file (opencode), never in argv. done-done can start claude (`claude -p --output-format json --json-schema <schema> --model <model> --no-session-persistence`, all tools off with `--tools ''`; tool-less calls also pass `--setting-sources ''` and run from a scratch folder, so no project settings or hooks load; read-only calls allow only `--tools Read,Grep,Glob` and pass `--setting-sources ''` and `--strict-mcp-config`, so no user settings or MCP servers load), opencode (`opencode run "Follow the attached instructions." --format json -m <provider/model> -f <prompt file>`, with the schema in the 0600 prompt file and every tool denied through `OPENCODE_PERMISSION` except on an edit step ([Drive by agent](#drive-by-agent), Scope)), pi (`pi -p --mode json --no-session --no-tools`, tools off for every read or probe call, since pi has no read-only mode that refuses outside reads, and `--model <model>` only when one is set) and gemini (`gemini -p "Follow the instructions on stdin." -o json -m <model> --approval-mode plan|auto_edit`, where `plan` is read-only for the probe and read steps and `auto_edit` is used for edit steps; `--skip-trust` is added only to the tool-less probe in its scratch folder, so repo calls keep Gemini's workspace trust gate) and codex (`codex exec --json --ephemeral [-m <model>]`, the answer being the last agent message of its JSONL events; an edit step runs in codex's `--sandbox workspace-write` with the run's evidence and scratch folders added by `--add-dir` and network on, so the drive can launch the app and talk to it, while it keeps your own codex config). Codex readers and probes run a separate `--no-daemon exec --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only` call, with project instructions disabled and your normal authentication untouched. Feature flags alone do not remove all tools: model metadata can force code-mode, collaboration and patch tools back on. done-done reads the CLI's public bundled catalog (`codex debug models --bundled`), writes a scratch-only copy with those capabilities removed, disables the remaining tool features and web search, and supplies it through `model_catalog_json`. A missing catalog, unsupported CLI or model not in that catalog fails with its cause; no unsafe fallback or copied login is used. `doctor --probe` now requires a real Codex answer, and `init` and maintain use the same clean reader. Native Codex image judging is not implemented; Codex image judging is available through `answerer:pi` with `openai-codex/gpt-6.1-sol` on your existing Codex login. `acp:<target>` runs `acpx --cwd <dir> --format json --json-strict --deny-all --non-interactive-permissions deny [--model <model>] <target> exec --file -`, with the prompt and schema on stdin; every tool is denied. pi has no permission gate for edit steps, so doctor adds a `tool_gate` warning row whenever pi is the agent: every pi tool runs without approval. codex's sandbox covers its shell too, so doctor adds a `tool_gate` warning row whenever codex is the agent: a drive's recipe tool can write only where the drive may (see [Drive by agent](#drive-by-agent), Scope).
|
|
102
102
|
|
|
103
103
|
### Add a journey from a description
|
|
104
104
|
|
|
@@ -463,7 +463,20 @@ done-done check --from pr-body.md # always judged
|
|
|
463
463
|
- `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.
|
|
464
464
|
- 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.
|
|
465
465
|
- `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.
|
|
466
|
-
- `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.
|
|
466
|
+
- `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. With no explicit `judge.fallback_route`, this route retains its built-in 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.
|
|
467
|
+
|
|
468
|
+
**Optional second route.** Global `judge.fallback_route` accepts the same routes as `judge.route` and is unset by default. For `claude-code`, a rate/usage limit, missing sign-in, or an account picker returning no eligible account triggers one retry of the **whole check** on that route. Invalid picker JSON/configuration, malformed model answers and other Claude errors still stop the call. With no fallback configured, Claude failures keep their existing error and no verdict. Other routes retry on their existing judge errors; an explicit fallback replaces MiniMax's built-in fallback. There is only one hop (no chains, and the same route is not retried). The result's `judge.route`, `model` and fallback `detail` name the judge that answered; if both fail, both causes are reported. The fallback uses its own `agent_config.<agent>.model` or default, **not** the primary's `judge.model`. Key routes therefore need their own default model to work as a fallback (`minimax:key` has one; `openai:key` does not).
|
|
469
|
+
|
|
470
|
+
For Codex/Sol on the already signed-in Pi CLI (no API key, paid-key route or new login):
|
|
471
|
+
|
|
472
|
+
```yaml
|
|
473
|
+
agent_config:
|
|
474
|
+
pi:
|
|
475
|
+
model: openai-codex/gpt-6.1-sol
|
|
476
|
+
judge:
|
|
477
|
+
route: claude-code
|
|
478
|
+
fallback_route: answerer:pi
|
|
479
|
+
```
|
|
467
480
|
|
|
468
481
|
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.
|
|
469
482
|
|
|
@@ -544,6 +557,22 @@ After those checks pass, each live image is sent beside its approved mock throug
|
|
|
544
557
|
|
|
545
558
|
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
559
|
|
|
560
|
+
#### Assets, colors, interactions and unlisted references
|
|
561
|
+
|
|
562
|
+
Screens are not the whole approval. Name every other approved item in `approved_requirements`, each with the live evidence it needs, or skip it with an explicit exclusion:
|
|
563
|
+
|
|
564
|
+
```yaml
|
|
565
|
+
approved_requirements:
|
|
566
|
+
- { kind: asset, name: logo, capture: logo.png, mock: design/logo.png } # judged beside its approved image
|
|
567
|
+
- { kind: color, name: brand-accent, capture: accent.png } # must be cited live evidence
|
|
568
|
+
- { kind: interaction, name: swipe-to-delete, capture: swipe.png }
|
|
569
|
+
- { kind: reference, name: design/spare.png, exclusion: owner dropped the spare illustration }
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`kind` is `asset`, `color`, `interaction` or `reference`. A row needs a unique `capture` filename (one cited, run-bound PNG/JPEG; optional `mock` sends it through the design judge) **or** `exclusion: <reason>`, the only way to skip it. An uncovered row fails the claim before any judge call, naming the item. Exclusions are reported under `approved_exclusions`. Receipts do not yet bind requirement captures; they are enforced by `check`, `receipt` and `verify` at run time.
|
|
573
|
+
|
|
574
|
+
**Behavior that tightens.** (1) Committed PNG/JPEG files under a top-level `design*/`, `mock*/`, `approved*/` or `prototype*/` folder that no `approved_design` `mock`, requirement `mock` or `kind: reference` row names fail every claim as "approved reference material is not in the approved_design manifest". (2) A claim whose outcome mentions design, mock, prototype or approved, with no `approved_design` manifest, no requirement `mock` and no `base_ref` baseline capture, fails with `not compared to approved result`; render, fit and dark-mode checks alone do not prove it. Manifests that list every reference and compare against it keep working unchanged.
|
|
575
|
+
|
|
547
576
|
#### Interaction checklist (opt-in)
|
|
548
577
|
|
|
549
578
|
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).
|
|
@@ -751,13 +780,14 @@ evidence:
|
|
|
751
780
|
local_root: ~/.done-done/evidence # evidence root for repos that set none (<local_root>/<app>)
|
|
752
781
|
judge: # done-done's own judge; check, receipt, verify and design always start it
|
|
753
782
|
# 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
|
|
783
|
+
# fallback_route: answerer:pi # optional; its agent_config.pi.model can be openai-codex/gpt-6.1-sol
|
|
754
784
|
model: '' # default: that agent's agent_config model, or openai-codex/gpt-6-luna for pi
|
|
755
785
|
floor: 0.7 # every judge's pick must reach this probability to count; below it the verdict is unsure (inconclusive), never a pass
|
|
756
786
|
# 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
|
|
757
787
|
# account_command: account-picker pick claude --json # optional shell command for Claude judge and agent calls (verify/maintain too), once per process; exported CLAUDE_CONFIG_DIR wins
|
|
758
788
|
```
|
|
759
789
|
|
|
760
|
-
`judge.account_command` must print one JSON object: `{"ok":true,"id":"pool-1","set":{"CLAUDE_CONFIG_DIR":"/absolute/existing/folder"},"unset":["ANTHROPIC_API_KEY"]}`. Only the selected folder is set, and each `unset` name is deleted from the spawned environment; it cannot unset `CLAUDE_CONFIG_DIR`. The detail names only the account id. A
|
|
790
|
+
`judge.account_command` must print one JSON object: `{"ok":true,"id":"pool-1","set":{"CLAUDE_CONFIG_DIR":"/absolute/existing/folder"},"unset":["ANTHROPIC_API_KEY"]}`. Only the selected folder is set, and each `unset` name is deleted from the spawned environment; it cannot unset `CLAUDE_CONFIG_DIR`. The detail names only the account id. A pick returning `ok: false` (or JSON `null`) is an unavailable Claude route and uses `judge.fallback_route` when configured; otherwise it stops with the same cause and no verdict. An invalid pick still stops the call. Neither case falls back to the default Claude login. Unset, it preserves normal Claude sign-in behavior; other agents and API-key routes do not run it.
|
|
761
791
|
|
|
762
792
|
An `agent_config` entry merges with the defaults field by field, so `claude: { model: claude-sonnet-5-5 }` keeps `effort: low`; the other maps merge entry by entry. `done-done doctor` shows which file set each agent setting.
|
|
763
793
|
|
|
@@ -778,6 +808,7 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
|
|
|
778
808
|
| `dom` | none; page-check records per capture, see [DOM assertions](#dom-assertions) |
|
|
779
809
|
| `look` | none; the look step's approved design reference (`tokens`, `max_control`), see [Look check](#look-check) |
|
|
780
810
|
| `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) |
|
|
811
|
+
| `approved_requirements` | none; approved assets, colors and interactions with the live capture each needs, or an explicit `exclusion`, see [Assets, colors, interactions](#assets-colors-interactions-and-unlisted-references) |
|
|
781
812
|
| `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) |
|
|
782
813
|
| `inputs` | `[]`; repo-relative paths or globs the drive must leave alone, see [Input guard](#input-guard) |
|
|
783
814
|
| `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.12 patch release
|
|
2
2
|
|
|
3
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.12 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
|
@@ -98,6 +98,9 @@ fresh session of its own, looks at each capture. `check` and `receipt` start the
|
|
|
98
98
|
and nothing turns it off: there is no result without it. When it says no judge is set, ask the user which
|
|
99
99
|
judge (and model) to use and save their answer with the command it prints; never choose one for them. When
|
|
100
100
|
the judge fails, report its error to the user; never call the work proven without a judge pass.
|
|
101
|
+
An explicitly configured `judge.fallback_route` can judge when Claude has no eligible account, sign-in or usage left;
|
|
102
|
+
`answerer:pi` with `agent_config.pi.model: openai-codex/gpt-6.1-sol` uses the existing Codex login.
|
|
103
|
+
Read `done-done --help` for fallback configuration; results name the route that actually answered.
|
|
101
104
|
|
|
102
105
|
## Request
|
|
103
106
|
|