done-done-axi 0.6.17 → 0.6.18
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 +20 -1
- package/dist/cli.js +51 -23
- package/docs/reference.md +17 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ The last line is your first result: your agent drives the real app, a fresh judg
|
|
|
29
29
|
|
|
30
30
|
[Website](https://umeranjum17.github.io/done-done-axi/) · [Quick start](#quick-start) · [Why](#why-not-just-green-tests) · [Reference](docs/reference.md) · [npm](https://www.npmjs.com/package/done-done-axi)
|
|
31
31
|
|
|
32
|
-
[](https://www.npmjs.com/package/done-done-axi) [](https://nodejs.org) [](#whats-next)
|
|
32
|
+
[](https://www.npmjs.com/package/done-done-axi) [](https://nodejs.org) [](#whats-next) [](https://x.com/umership)
|
|
33
33
|
|
|
34
34
|
</div>
|
|
35
35
|
|
|
@@ -43,6 +43,25 @@ You need **[Node 22.18+](https://nodejs.org) and git**, plus your own signed-in
|
|
|
43
43
|
|
|
44
44
|
Commit `.done-done.yaml` and the verify skill so later runs and your teammates use the same recipe. Captures go to your judge's provider, on your plan or key.
|
|
45
45
|
|
|
46
|
+
## For installers
|
|
47
|
+
|
|
48
|
+
Installing done-done from another installer (a fleet bootstrap, a Juggernaut-style one-command install) takes three calls and no prompts. Pin the version:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
npm install -g done-done-axi@0.6.18
|
|
52
|
+
done-done init --no-skill --agent <agent> --model <model> --judge <route> --judge-model <model>
|
|
53
|
+
done-done doctor --setup --json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `done-done --version` prints the installed version (here `0.6.18`); check it before anything else.
|
|
57
|
+
- `init --no-skill` writes `.done-done.yaml` and the global `~/.done-done/config.yaml`, and starts no agent: it skips the verify skill, so it spends nothing and asks nothing. `<route>` is `answerer:<agent>` or a bare agent name. Exit `0` when the agent is set and the config is valid, `2` when it is not.
|
|
58
|
+
- `doctor --setup --json` is install health: exit `0` when git, the config, the evidence root, the agent and the judge are ok, `2` when one of them is not. The verify skill and the journey rows are warnings, so a fresh install passes.
|
|
59
|
+
- Exit codes: `0` pass, `2` usage or config error ([all codes](docs/reference.md#exit-codes)).
|
|
60
|
+
- Re-running `init` changes nothing: a second call writes no file, reports the config unchanged and exits `0`.
|
|
61
|
+
- Real proof comes later: the user records a core journey, `done-done init --journey <name> --core -- <command>`, and it is their agent and judge that drive and check it.
|
|
62
|
+
|
|
63
|
+
The verify skill is not part of an install: the first plain `done-done init` writes it from the repo's own files.
|
|
64
|
+
|
|
46
65
|
## Why: not just green tests
|
|
47
66
|
|
|
48
67
|
| | Green tests, or an agent's word | done-done |
|
package/dist/cli.js
CHANGED
|
@@ -112,6 +112,9 @@ const DOCTOR_TEXT = `doctor changes nothing except the --probe call record (the
|
|
|
112
112
|
judge_choice (the judge, and how far it is from the author: another agent, another model, or session-only), ffprobe (video evidence only).
|
|
113
113
|
--probe also asks the agent one JSON-schema question (one call at the agent's model: its cheap default where it has one, else its own; set agent_config.<agent>.model to choose; its record goes to
|
|
114
114
|
$DONE_DONE_HOME/agents/) and then requires a valid answer.
|
|
115
|
+
--setup is install health: the required rows are git, config, evidence_root, agent and judge, and the verify skill
|
|
116
|
+
or a repo with no journeys yet is a warning, never a failure. real_product, core_journey and upgrade_journey report
|
|
117
|
+
skipped (--setup) until the user records one; plain doctor requires them.
|
|
115
118
|
doctor lists every check; --full changes nothing. A missing verify_skill or ffprobe, or a warning, never fails it.`;
|
|
116
119
|
const CONFIG_TEXT = `config: ~/.done-done/config.yaml (global; DONE_DONE_HOME moves it) and .done-done.yaml (the repo), repo over global.
|
|
117
120
|
agent (either file): claude, codex, opencode, pi, gemini or acp:<target>, set by the user (done-done init --agent); done-done never picks one.
|
|
@@ -160,6 +163,8 @@ const FLAGS = {
|
|
|
160
163
|
'--model <model>': 'with --agent: that agent\'s model, saved as agent_config.<agent>.model (opencode: provider/model)',
|
|
161
164
|
'--judge <agent>': `the judge done-done starts (${JUDGES.join(' or ')}), saved as judge.route in ~/.done-done/config.yaml (any agent and model, the author's own too); without it, init asks in a terminal`,
|
|
162
165
|
'--judge-model <model>': 'with --judge <agent>: the judge\'s model, saved as judge.model',
|
|
166
|
+
'--no-skill': 'with --agent: write the config but skip the agent-written verify skill (no agent call, no spend); the user writes it later with done-done init',
|
|
167
|
+
'--setup': 'install health: git, config, evidence_root, agent and judge only; the verify skill and a repo with no journeys are warnings, and the journey rows are skipped',
|
|
163
168
|
'--before': 'record HEAD at PreToolUse; without it consume the matching PostToolUse event and capture the commit',
|
|
164
169
|
'--after-commit': 'with --hook claude, opt in to Bash lifecycle hooks that rerun affected journeys after a commit and propose missing skill coverage (no agent or judge started)',
|
|
165
170
|
'--hook <agent>': 'also add a SessionStart hook to .claude/settings.json (claude), .codex/hooks.json (codex) or .gemini/settings.json (gemini) that shows `done-done` to each session, or a plugin to .opencode/plugins (opencode) or an extension to .pi/extensions (pi) that adds it to the first message of each session (opt-in)',
|
|
@@ -171,7 +176,8 @@ const COMMANDS = {
|
|
|
171
176
|
['done-done init', 'write a minimal .done-done.yaml (evidence lives outside the repo) and, when absent, a commented ~/.done-done/config.yaml'],
|
|
172
177
|
['done-done init', 'have the agent write .agents/skills/done-done-verify-<app> from this repo\'s own proof paths (never overwrites; no agent, no skill)'],
|
|
173
178
|
['done-done init --agent opencode --model opencode-go/muse-spark-1.3-contributor', 'set the coding agent done-done starts and its model (in a terminal, init asks when none is set)'],
|
|
174
|
-
['done-done init --judge pi --judge-model openai-codex/gpt-6-luna', 'set the judge done-done starts and its model (in a terminal, init asks when none is set)'],
|
|
179
|
+
['done-done init --judge pi --judge-model openai-codex/gpt-6-luna', 'set the judge done-done starts and its model (in a terminal, init asks when none is set); answerer:pi, the route check docs use, saves the same route'],
|
|
180
|
+
['done-done init --no-skill --agent pi --model <model> --judge answerer:pi --judge-model <model>', 'write the config and settings without asking the agent for the verify skill: what an installer runs'],
|
|
175
181
|
['done-done init --hook claude', 'also show this repo\'s done-done state at each Claude Code session start'],
|
|
176
182
|
['done-done init --hook claude --after-commit', 'also opt in to rerun affected journeys after a Bash commit; propose missing coverage without editing the skill'],
|
|
177
183
|
['done-done init --hook codex', 'also show this repo\'s done-done state at each Codex session start'],
|
|
@@ -182,15 +188,16 @@ const COMMANDS = {
|
|
|
182
188
|
['done-done init --journey <name> --core -- <command...>', 'record the core user journey: the one thing a user cannot live without, end to end on the real target'],
|
|
183
189
|
['done-done init --journey upgrade --upgrade -- <command...>', 'record the release path: install the previous shipped version, upgrade over it in place, drive the core journey'],
|
|
184
190
|
],
|
|
185
|
-
flags: ['--agent <agent>', '--model <model>', '--judge <agent>', '--judge-model <model>', '--hook <agent>', '--after-commit', '--journey <name>', '--core', '--upgrade', '-- <command...>'],
|
|
191
|
+
flags: ['--agent <agent>', '--model <model>', '--judge <agent>', '--judge-model <model>', '--hook <agent>', '--after-commit', '--journey <name>', '--core', '--upgrade', '--no-skill', '-- <command...>'],
|
|
186
192
|
more: [JOURNEYS_TEXT, CONFIG_TEXT],
|
|
187
193
|
},
|
|
188
194
|
doctor: {
|
|
189
195
|
usage: [
|
|
190
196
|
['done-done doctor', 'check this repo is worth driving: git, config, evidence root, skill, agent, judge, ffprobe'],
|
|
191
197
|
['done-done doctor --probe', 'also ask the configured agent one question to prove it answers'],
|
|
198
|
+
['done-done doctor --setup', 'install health: exit 0 once git, config, evidence root, agent and judge are ok, with the journey rows skipped until the user records one'],
|
|
192
199
|
],
|
|
193
|
-
flags: ['--probe'],
|
|
200
|
+
flags: ['--probe', '--setup'],
|
|
194
201
|
more: [DOCTOR_TEXT, CONFIG_TEXT],
|
|
195
202
|
},
|
|
196
203
|
run: {
|
|
@@ -3510,7 +3517,8 @@ const ACP_TARGET = 'acp:<target>';
|
|
|
3510
3517
|
// Save the user's agent or judge (and model) in the global config, in place, keeping its comments and every other key.
|
|
3511
3518
|
function saveChoice(kind, { agent, model }) {
|
|
3512
3519
|
const file = join(home(), 'config.yaml');
|
|
3513
|
-
|
|
3520
|
+
const original = readFileSync(file, 'utf8');
|
|
3521
|
+
let text = original;
|
|
3514
3522
|
if (kind === 'agent') {
|
|
3515
3523
|
const example = /^# agent: .*$/m;
|
|
3516
3524
|
if (!parseDocument(text).has('agent'))
|
|
@@ -3531,8 +3539,12 @@ function saveChoice(kind, { agent, model }) {
|
|
|
3531
3539
|
else
|
|
3532
3540
|
doc.deleteIn(['judge', 'model']);
|
|
3533
3541
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3542
|
+
// A re-run with the same choices writes nothing: the file already says it.
|
|
3543
|
+
const next = doc.toString({ lineWidth: 0 });
|
|
3544
|
+
if (next !== original) {
|
|
3545
|
+
writeFileSync(`${file}.tmp`, next);
|
|
3546
|
+
renameSync(`${file}.tmp`, file);
|
|
3547
|
+
}
|
|
3536
3548
|
return `${kind === 'agent' ? agent : routeOf(agent)}${model ? ` (model ${model})` : ''} saved to ${file}`;
|
|
3537
3549
|
}
|
|
3538
3550
|
// Why a typed model is not one opencode lists, or '' when it is listed or unchecked (only opencode is checked; pi's list comes back cut short on a pipe).
|
|
@@ -3667,18 +3679,24 @@ function checkJudge(cfg, agent, model) {
|
|
|
3667
3679
|
if (!model && !modelFor(cfg, agent))
|
|
3668
3680
|
throw new UsageError(`judge: ${agent} has no default model, so a judge needs one`, `Save the judge with its model: done-done init --judge ${agent} --judge-model <model>`);
|
|
3669
3681
|
}
|
|
3670
|
-
const commitFirst = (skill) => `.done-done.yaml and ${skill} before your first capture: check refuses proof from a tree with uncommitted changes`;
|
|
3682
|
+
const commitFirst = (skill, noSkill = false) => `${noSkill ? '.done-done.yaml' : `.done-done.yaml and ${skill}`} before your first capture: check refuses proof from a tree with uncommitted changes`;
|
|
3683
|
+
// init takes the route check docs use (answerer:pi) as well as the bare agent (pi); both save the same judge.route,
|
|
3684
|
+
// and any other form stays an error.
|
|
3685
|
+
const initJudge = (v) => (v.startsWith('answerer:') && routeOf(v.slice('answerer:'.length)) === v ? v.slice('answerer:'.length) : v);
|
|
3671
3686
|
// An absolute path inside the repo in a recipe: the recipe is committed, so it would name this checkout only.
|
|
3672
3687
|
const absoluteInside = (command, repo) => [...command.matchAll(/(?:^|[\s"'=])(\/[^\s"'`;|&<>]*)/g)].map((m) => m[1]).find((p) => p === repo || p.startsWith(`${repo}/`));
|
|
3673
3688
|
async function cmdInit(argv) {
|
|
3674
3689
|
const dash = argv.indexOf('--');
|
|
3675
3690
|
const { values } = parseArgs({
|
|
3676
3691
|
args: dash < 0 ? argv : argv.slice(0, dash),
|
|
3677
|
-
options: { agent: { type: 'string' }, model: { type: 'string' }, judge: { type: 'string' }, 'judge-model': { type: 'string' }, hook: { type: 'string' }, 'after-commit': { type: 'boolean' }, journey: { type: 'string' }, core: { type: 'boolean' }, upgrade: { type: 'boolean' } },
|
|
3692
|
+
options: { agent: { type: 'string' }, model: { type: 'string' }, judge: { type: 'string' }, 'judge-model': { type: 'string' }, hook: { type: 'string' }, 'after-commit': { type: 'boolean' }, journey: { type: 'string' }, core: { type: 'boolean' }, upgrade: { type: 'boolean' }, 'no-skill': { type: 'boolean' } },
|
|
3678
3693
|
allowPositionals: false,
|
|
3679
3694
|
});
|
|
3680
3695
|
const parts = dash < 0 ? [] : argv.slice(dash + 1);
|
|
3681
3696
|
const journey = values.journey ?? null;
|
|
3697
|
+
// --no-skill: an installer writes the config without the agent-written skill, so it costs no agent call.
|
|
3698
|
+
const noSkill = values['no-skill'] === true;
|
|
3699
|
+
const judge = values.judge === undefined ? undefined : initJudge(values.judge);
|
|
3682
3700
|
if (values.hook !== undefined && !Object.hasOwn(HOOKS, values.hook))
|
|
3683
3701
|
throw new UsageError(`--hook ${values.hook} is not supported; the supported values are ${Object.keys(HOOKS).join(', ')}`, `Run: done-done init --hook <${Object.keys(HOOKS).join('|')}>`);
|
|
3684
3702
|
if (values['after-commit'] && values.hook !== 'claude')
|
|
@@ -3687,7 +3705,7 @@ async function cmdInit(argv) {
|
|
|
3687
3705
|
throw new UsageError('--model needs --agent', `Run: done-done init --agent <agent> --model ${values.model}`);
|
|
3688
3706
|
if (values.agent !== undefined)
|
|
3689
3707
|
checkChoice(values.agent, values.model ?? '');
|
|
3690
|
-
if (values['judge-model'] !== undefined &&
|
|
3708
|
+
if (values['judge-model'] !== undefined && judge === undefined)
|
|
3691
3709
|
throw new UsageError('--judge-model needs --judge', `Run: done-done init --judge <agent> --judge-model ${values['judge-model']}`);
|
|
3692
3710
|
if (journey === null && parts.length)
|
|
3693
3711
|
throw new UsageError('init needs --journey <name> to record a command after --');
|
|
@@ -3700,9 +3718,9 @@ async function cmdInit(argv) {
|
|
|
3700
3718
|
if (journey !== null && codeOnly(commandOf(parts)))
|
|
3701
3719
|
throw new UsageError(`init --journey ${journey}: ${commandOf(parts)} is code-only: ${RULE}, so check and gate never count its run as proof; nothing was written`, `Record a command that drives the real app, CLI or site the way a user does and saves screenshots, a recording or its transcript into $DONE_DONE_EVIDENCE_ROOT: done-done init --journey ${journey} -- '<that command>'`);
|
|
3702
3720
|
// Only the judge named: that is a machine setting, so it changes the global config alone, in any directory.
|
|
3703
|
-
if (
|
|
3721
|
+
if (judge !== undefined && values.agent === undefined && values.hook === undefined && journey === null) {
|
|
3704
3722
|
const cfg = loadConfig(process.cwd());
|
|
3705
|
-
const choice = { agent:
|
|
3723
|
+
const choice = { agent: judge, model: values['judge-model'] ?? (cfg.judge.route === routeOf(judge) ? cfg.judge.model : '') };
|
|
3706
3724
|
checkJudge(cfg, choice.agent, choice.model);
|
|
3707
3725
|
const globalConfig = writeGlobal();
|
|
3708
3726
|
emit({ command: 'init', result: 'pass', global_config: globalConfig, judge: saveChoice('judge', choice), help: ['Set up a repo: cd <your app> && done-done init'] });
|
|
@@ -3722,7 +3740,7 @@ async function cmdInit(argv) {
|
|
|
3722
3740
|
if (choice)
|
|
3723
3741
|
cfg.agent_config[choice.agent] = { ...cfg.agent_config[choice.agent], ...(choice.model && { model: choice.model }) };
|
|
3724
3742
|
// The judge likewise: --judge, else with none set the user's pick in a terminal; checked that done-done can judge through it.
|
|
3725
|
-
const judgeChoice =
|
|
3743
|
+
const judgeChoice = judge !== undefined ? { agent: judge, model: values['judge-model'] ?? (cfg.judge.route === routeOf(judge) ? cfg.judge.model : '') } : !cfg.judge.route && interactive() ? await pick('judge', { ...cfg, agent: choice?.agent ?? cfg.agent }) : undefined;
|
|
3726
3744
|
if (judgeChoice)
|
|
3727
3745
|
checkJudge({ ...cfg, agent: choice?.agent ?? cfg.agent }, judgeChoice.agent, judgeChoice.model);
|
|
3728
3746
|
const writeAgent = () => {
|
|
@@ -3780,7 +3798,7 @@ async function cmdInit(argv) {
|
|
|
3780
3798
|
const globalConfig = writeGlobal();
|
|
3781
3799
|
const ag = writeAgent();
|
|
3782
3800
|
const jg = writeJudge();
|
|
3783
|
-
const verify = await scaffoldVerify(repo, loadConfig(repo));
|
|
3801
|
+
const verify = noSkill ? { status: 'skipped (--no-skill)' } : await scaffoldVerify(repo, loadConfig(repo));
|
|
3784
3802
|
const agentHook = writeHook();
|
|
3785
3803
|
const code = ag.code !== PASS ? ag.code : verify.failed ? FAIL : PASS;
|
|
3786
3804
|
emit({
|
|
@@ -3797,7 +3815,7 @@ async function cmdInit(argv) {
|
|
|
3797
3815
|
...(renamed && { skill_renamed: renamed }),
|
|
3798
3816
|
verify_skill: verify.status,
|
|
3799
3817
|
...agentHook.out,
|
|
3800
|
-
help: [`Capture proof: done-done run ${journey}, then: ${check}`, ...journeyAsks(repo, fresh), ...agentHook.help, ...ag.help, ...jg.help, ...(verify.failed ? [SCAFFOLD_HELP] : []), `Commit ${commitFirst(fresh.skill)}`],
|
|
3818
|
+
help: [`Capture proof: done-done run ${journey}, then: ${check}`, ...journeyAsks(repo, fresh), ...agentHook.help, ...ag.help, ...jg.help, ...(verify.failed ? [SCAFFOLD_HELP] : []), `Commit ${commitFirst(fresh.skill, noSkill)}`],
|
|
3801
3819
|
});
|
|
3802
3820
|
return code;
|
|
3803
3821
|
}
|
|
@@ -3821,7 +3839,7 @@ app: ${JSON.stringify(cfg.app)}
|
|
|
3821
3839
|
const globalConfig = writeGlobal();
|
|
3822
3840
|
const ag = writeAgent();
|
|
3823
3841
|
const jg = writeJudge();
|
|
3824
|
-
const verify = await scaffoldVerify(repo, loadConfig(repo));
|
|
3842
|
+
const verify = noSkill ? { status: 'skipped (--no-skill)' } : await scaffoldVerify(repo, loadConfig(repo));
|
|
3825
3843
|
const agentHook = writeHook();
|
|
3826
3844
|
const code = ag.code !== PASS ? ag.code : verify.failed ? FAIL : PASS;
|
|
3827
3845
|
emit({
|
|
@@ -3846,7 +3864,7 @@ app: ${JSON.stringify(cfg.app)}
|
|
|
3846
3864
|
...ag.help,
|
|
3847
3865
|
...jg.help,
|
|
3848
3866
|
...(verify.failed ? [SCAFFOLD_HELP] : []),
|
|
3849
|
-
`Commit ${commitFirst(cfg.skill)}`,
|
|
3867
|
+
`Commit ${commitFirst(cfg.skill, noSkill)}`,
|
|
3850
3868
|
],
|
|
3851
3869
|
});
|
|
3852
3870
|
return code;
|
|
@@ -3913,14 +3931,19 @@ const modelPhrase = (agent, model) => (model ? `model ${model}` : `its own defau
|
|
|
3913
3931
|
// Is this repo worth driving: git, a valid config, a writable evidence root, the agent and the judge are
|
|
3914
3932
|
// required; the verify skill and ffprobe are listed but never fail it.
|
|
3915
3933
|
// doctor changes nothing except the --probe call record (the agent itself may keep its own session files): a missing repo or config is a finding, never a crash.
|
|
3934
|
+
// --setup is install health: the verify skill and a repo with no journeys are warnings, and the journey rows an install
|
|
3935
|
+
// does not have yet are skipped, so a freshly configured repo exits 0.
|
|
3916
3936
|
async function cmdDoctor(argv) {
|
|
3917
3937
|
let values;
|
|
3918
3938
|
try {
|
|
3919
|
-
({ values } = parseArgs({ args: argv, options: { probe: { type: 'boolean' } }, allowPositionals: false }));
|
|
3939
|
+
({ values } = parseArgs({ args: argv, options: { probe: { type: 'boolean' }, setup: { type: 'boolean' } }, allowPositionals: false }));
|
|
3920
3940
|
}
|
|
3921
3941
|
catch (e) {
|
|
3922
3942
|
throw new UsageError(e.message, 'Run `done-done doctor` (or `done-done doctor --probe`) with no other arguments');
|
|
3923
3943
|
}
|
|
3944
|
+
const setup = values.setup === true;
|
|
3945
|
+
// --setup reports a journey row an install does not have yet as skipped instead of failing it.
|
|
3946
|
+
const setupSkipped = (check, why) => ({ check, status: 'skipped', detail: `skipped (--setup): ${why}`, fix: '' });
|
|
3924
3947
|
let repo = null;
|
|
3925
3948
|
try {
|
|
3926
3949
|
repo = git(process.cwd(), 'rev-parse', '--show-toplevel');
|
|
@@ -3970,8 +3993,8 @@ async function cmdDoctor(argv) {
|
|
|
3970
3993
|
else
|
|
3971
3994
|
checks.push({
|
|
3972
3995
|
check: 'verify_skill',
|
|
3973
|
-
status: 'missing',
|
|
3974
|
-
detail: `no ${shown(repo, skillFile)}; checks still pass with "no verify skill"`,
|
|
3996
|
+
status: setup ? 'warning' : 'missing',
|
|
3997
|
+
detail: `no ${shown(repo, skillFile)}; ${setup ? 'doctor --setup does not need it: the user writes it later with done-done init' : 'checks still pass with "no verify skill"'}`,
|
|
3975
3998
|
fix: `Write ${shown(repo, skillFile)} describing how to drive this app (a later done-done init will scaffold it)`,
|
|
3976
3999
|
});
|
|
3977
4000
|
}
|
|
@@ -4077,8 +4100,10 @@ async function cmdDoctor(argv) {
|
|
|
4077
4100
|
const code = codeOnlyJourneys(cfg.journeys);
|
|
4078
4101
|
const fix = `Record a journey that drives the real app and saves screenshots, a recording or its transcript into $DONE_DONE_EVIDENCE_ROOT: done-done init --journey <name> -- '<command>'; then delete ${code.join(', ')} from .done-done.yaml`;
|
|
4079
4102
|
checks.push(configFault(cfg.journeys) ? { check: 'real_product', status: 'missing', detail: configFault(cfg.journeys), fix }
|
|
4080
|
-
:
|
|
4081
|
-
:
|
|
4103
|
+
: setup && !names.length ? { check: 'real_product', status: 'warning', detail: 'no journeys yet: the user records a core journey after install, for real proof', fix: CORE_FIX }
|
|
4104
|
+
: setup ? setupSkipped('real_product', `${names.join(', ')} ${names.length > 1 ? 'drive' : 'drives'} the product`)
|
|
4105
|
+
: code.length ? { check: 'real_product', status: 'warning', detail: `${code.join(', ')} ${code.length > 1 ? 'are' : 'is'} code-only (${RULE}): proof only from ${code.length > 1 ? 'them' : 'it'} is refused; ${names.filter((n) => !code.includes(n)).join(', ')} drive${names.length - code.length > 1 ? '' : 's'} the product`, fix: `Cite captures from the journeys that drive the product; delete ${code.join(', ')} from .done-done.yaml if it is not needed` }
|
|
4106
|
+
: { check: 'real_product', status: 'ok', detail: names.length ? `${names.join(', ')} drive${names.length > 1 ? '' : 's'} the product` : 'no journeys: verify has the agent drive the verify skill\'s features', fix: '' });
|
|
4082
4107
|
}
|
|
4083
4108
|
// The core journey and, for an app that ships an installable artifact, the upgrade-in-place journey: named in config or the feature map.
|
|
4084
4109
|
if (!cfg || !repo) {
|
|
@@ -4089,15 +4114,18 @@ async function cmdDoctor(argv) {
|
|
|
4089
4114
|
const core = namedJourney('core', cfg.journeys, map);
|
|
4090
4115
|
checks.push(core
|
|
4091
4116
|
? { check: 'core_journey', status: 'ok', detail: `${core} is the core user journey`, fix: '' }
|
|
4092
|
-
:
|
|
4117
|
+
: setup ? setupSkipped('core_journey', 'no core user journey yet: the user records one after install, for real proof')
|
|
4118
|
+
: { check: 'core_journey', status: 'missing', detail: `no core user journey is named: a journey with core: true that drives the product, or a ${MARK.core} entry in ${cfg.skill}/features/README.md`, fix: CORE_FIX });
|
|
4093
4119
|
const ships = shipped(repo);
|
|
4094
4120
|
const upgrade = namedJourney('upgrade', cfg.journeys, map);
|
|
4095
4121
|
checks.push(!ships.length
|
|
4096
4122
|
? { check: 'upgrade_journey', status: 'ok', detail: 'no tracked file shows an installable or upgradable artifact, so no upgrade journey is needed', fix: '' }
|
|
4097
4123
|
: upgrade
|
|
4098
4124
|
? { check: 'upgrade_journey', status: 'ok', detail: `${upgrade} upgrades in place (the app ships ${ships.slice(0, 3).join('; ')})`, fix: '' }
|
|
4099
|
-
:
|
|
4125
|
+
: setup ? setupSkipped('upgrade_journey', `the app ships an installable artifact (${ships.slice(0, 3).join('; ')}) and no upgrade journey is recorded yet: record it before the next release`)
|
|
4126
|
+
: { check: 'upgrade_journey', status: 'missing', detail: `the app ships an installable artifact (${ships.slice(0, 3).join('; ')}${ships.length > 3 ? `; +${ships.length - 3} more` : ''}) and no upgrade journey is named: a fresh install alone is no release proof`, fix: UPGRADE_FIX });
|
|
4100
4127
|
}
|
|
4128
|
+
// --setup keeps the same required list; the journey rows it skips are never 'missing', so only a real config fault fails it.
|
|
4101
4129
|
const required = ['git', 'config', 'evidence_root', 'real_product', 'core_journey', 'upgrade_journey', 'agent', 'judge', ...(values.probe ? ['probe'] : [])];
|
|
4102
4130
|
const missingRequired = checks.filter((c) => c.status === 'missing' && required.includes(c.check));
|
|
4103
4131
|
const result = { command: 'doctor', result: missingRequired.length ? 'fail' : 'pass', checks };
|
package/docs/reference.md
CHANGED
|
@@ -24,6 +24,16 @@ done-done init --agent opencode --model opencode-go/muse-spark-1.3-contributor
|
|
|
24
24
|
|
|
25
25
|
`init --agent <agent> [--model <model>]` saves the coding agent done-done starts, and its model, into the global `~/.done-done/config.yaml` (`agent` and `agent_config.<agent>.model`), in place, keeping its comments and every other key; the result's `agent` says what was saved. Without `--agent`, when no agent is set, `init` asks in a terminal (a numbered list of agents, then of models; a wrong answer is named before the list asks again, and a typed opencode model must be one `opencode models` lists, or, when opencode is not on PATH, it is saved with a notice that it was not checked; any other agent's model, pi's included, is saved with that notice too) before it writes anything; elsewhere it writes the rest of the config, reports `agent: not set` with the line to ask the user and the save command, and exits 2. When a repo's `.done-done.yaml` sets a different agent, the result's `agent` says the saved agent is overridden by that one. An agent done-done cannot start, `--model` without `--agent`, or an opencode model that is not `provider/model` exits 2 before any write. done-done never picks an agent for you (see [Agents](#agents)). Headless `opencode run` needs `--auto` (or allows the evidence root, `~/.done-done`, in opencode's `external_directory` permission) for done-done to run.
|
|
26
26
|
|
|
27
|
+
For an installer, set everything in one non-interactive call and skip the agent-written skill:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
done-done init --no-skill --agent pi --model <model> --judge answerer:pi --judge-model <model>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`--no-skill` writes the project config and saves the agent and judge settings exactly as `init` does above, and then writes no verify skill: no agent is started, so it costs no call, and the result says `verify_skill: skipped (--no-skill)`. It exits 0 when the agent is set and the config is valid, and 2 when no agent is set, as `init` always does. It combines with `--journey` (the journey is recorded and the skill is still skipped). Nothing in it asks a question, reads a secret or needs a terminal, so re-running it is a no-op: a second run writes no file, reports `config: unchanged` and still exits 0. The user records the verify skill later with a plain `done-done init`, and a core journey for real proof with `done-done init --journey <name> --core -- <command>`.
|
|
34
|
+
|
|
35
|
+
`init --judge` also takes the route form the [check docs](#check-a-claim) use, `answerer:<agent>`, as well as the bare agent name: `--judge answerer:pi` stores the same `judge.route: answerer:pi` that `--judge pi` stores. `check --judge` already takes only the route form.
|
|
36
|
+
|
|
27
37
|
Record a journey's capture recipe in the repo so any checkout can re-run the proof:
|
|
28
38
|
|
|
29
39
|
```sh
|
|
@@ -94,6 +104,12 @@ done-done doctor --probe
|
|
|
94
104
|
|
|
95
105
|
`--probe` also asks the agent one JSON-schema question (one call at its model) and adds a `probe` item that is then required: `ok` only when the agent answers with a valid object. Codex is the exception: it cannot be asked tool-less, so its `probe` row is a `warning` that gives the reason and asks nothing (see [Agents](#agents)). The call record goes to `$DONE_DONE_HOME/agents/` (mode 0600); it keeps the agent, model, effort, extra flags, binary, schema, tools, cwd, timeout, argv, prompt size in bytes, timing, exit code, answer or error, and the last 2000 characters of stderr, never the prompt text. A failed probe row shows one clean line: the agent's last line that names an error, or else its last meaningful line, with no colour codes or stack frames and at most 200 characters. The raw stderr stays in the record.
|
|
96
106
|
|
|
107
|
+
```sh
|
|
108
|
+
done-done doctor --setup
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`--setup` is install health: the answer an installer needs after `init --no-skill`, with the same rows, detail lines and `--json` output. The required rows are `git`, `config`, `evidence_root`, `agent` and `judge`; a missing `verify_skill` and a repo with no journeys yet are `warning`s, and `real_product`, `core_journey` and `upgrade_journey` report `skipped (--setup)` rather than failing, since the user records a core journey after install for real proof. It exits 0 when every required row is ok and 2 otherwise, exactly like `doctor`, and plain `doctor` is unchanged.
|
|
112
|
+
|
|
97
113
|
#### Agents
|
|
98
114
|
|
|
99
115
|
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.
|
|
@@ -724,7 +740,7 @@ Running `done-done` with no subcommand prints the repo's config state (`config:
|
|
|
724
740
|
| 3 | inconclusive (could not be proven either way) |
|
|
725
741
|
| 4 | the run is still running, or a `verify` run waits at its gate (`verify`, `maintain --no-wait`, `rerun`, `status`, `respond`) |
|
|
726
742
|
|
|
727
|
-
`doctor` exits 2 when a required item (`git`, `config`, `evidence_root`, `real_product`, `core_journey`, `upgrade_journey`, `agent`, `judge`, and `probe` with `--probe`) is missing.
|
|
743
|
+
`doctor` exits 2 when a required item (`git`, `config`, `evidence_root`, `real_product`, `core_journey`, `upgrade_journey`, `agent`, `judge`, and `probe` with `--probe`) is missing. With `--setup` the required items are `git`, `config`, `evidence_root`, `agent` and `judge`: the journey rows report `skipped (--setup)` instead of failing, so a freshly installed repo exits 0.
|
|
728
744
|
|
|
729
745
|
### Merge gate on the desktop
|
|
730
746
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "done-done-axi",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.18",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Prove your app really works: done-done drives your app the way a user does, a fresh judge checks every capture, and the proof is tied to a commit.",
|
|
6
6
|
"type": "module",
|