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 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
- [![npm](https://img.shields.io/npm/v/done-done-axi?label=done-done&color=5754d7)](https://www.npmjs.com/package/done-done-axi) [![Node 22.18+](https://img.shields.io/badge/node-22.18%2B-3fb950)](https://nodejs.org) [![Early preview](https://img.shields.io/badge/status-preview-d29922)](#whats-next)
32
+ [![npm](https://img.shields.io/npm/v/done-done-axi?label=done-done&color=5754d7)](https://www.npmjs.com/package/done-done-axi) [![Node 22.18+](https://img.shields.io/badge/node-22.18%2B-3fb950)](https://nodejs.org) [![Early preview](https://img.shields.io/badge/status-preview-d29922)](#whats-next) [![X @umership](https://img.shields.io/badge/X-@umership-000000?logo=x&logoColor=white)](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
- let text = readFileSync(file, 'utf8');
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
- writeFileSync(`${file}.tmp`, doc.toString({ lineWidth: 0 }));
3535
- renameSync(`${file}.tmp`, file);
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 && values.judge === 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 (values.judge !== undefined && values.agent === undefined && values.hook === undefined && journey === null) {
3721
+ if (judge !== undefined && values.agent === undefined && values.hook === undefined && journey === null) {
3704
3722
  const cfg = loadConfig(process.cwd());
3705
- const choice = { agent: values.judge, model: values['judge-model'] ?? (cfg.judge.route === routeOf(values.judge) ? cfg.judge.model : '') };
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 = values.judge !== undefined ? { agent: values.judge, model: values['judge-model'] ?? (cfg.judge.route === routeOf(values.judge) ? cfg.judge.model : '') } : !cfg.judge.route && interactive() ? await pick('judge', { ...cfg, agent: choice?.agent ?? cfg.agent }) : undefined;
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
- : 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` }
4081
- : { 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: '' });
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
- : { 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 });
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
- : { 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 });
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.17",
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",