@pmelab/gtd 58.2.0 → 59.0.1

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.
Files changed (150) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +120 -395
  3. package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
  4. package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
  5. package/dist/flows.mjs +1 -1
  6. package/dist/gtd.bundle.mjs +484 -497
  7. package/dist/testkit/gherkin.mjs +145 -65
  8. package/dist/testkit.mjs +1 -1
  9. package/dist/wire.mjs +1 -1
  10. package/package.json +3 -42
  11. package/skills/authoring/SKILL.md +34 -27
  12. package/src/cli/Cli.test.ts +148 -71
  13. package/src/cli/Cli.ts +384 -463
  14. package/src/cli/Help.test.ts +47 -0
  15. package/src/cli/Help.ts +70 -0
  16. package/src/cli/Program.test.ts +127 -33
  17. package/src/cli/Program.ts +70 -25
  18. package/src/edge/Edge.ts +4 -4
  19. package/src/emit/Emit.test.ts +1 -1
  20. package/src/emit/Emit.ts +1 -1
  21. package/src/flags/index.test.ts +0 -13
  22. package/src/flags/index.ts +1 -3
  23. package/src/flows/factory.ts +3 -1
  24. package/src/gherkin/index.ts +135 -71
  25. package/src/gherkin/messages.test.ts +90 -0
  26. package/src/gherkin/messages.ts +84 -0
  27. package/src/install/Install.test.ts +14 -11
  28. package/src/install/Install.ts +38 -15
  29. package/src/install/index.ts +1 -2
  30. package/src/replay/Replay.test.ts +1 -1
  31. package/src/replay/index.ts +1 -1
  32. package/src/step/planStart.ts +6 -1
  33. package/src/testkit/TestRepo.test.ts +47 -2
  34. package/src/testkit/TestRepo.ts +51 -6
  35. package/src/twins/EmittedScriptRecognizer.ts +5 -8
  36. package/src/twins/FlowScripts.ts +1 -1
  37. package/src/wire/Land.ts +1 -1
  38. package/src/workflow/index.ts +1 -0
  39. package/src/workflow/load.test.ts +138 -113
  40. package/src/workflow/load.ts +7 -7
  41. package/src/workflow/skills.test.ts +5 -5
  42. package/src/workflow/skills.ts +1 -1
  43. package/src/workflow/vars.test.ts +30 -3
  44. package/src/workflow/vars.ts +34 -2
  45. package/src/workflow-kit/index.ts +1 -1
  46. package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
  47. package/dist/chunks/review-Bfaror38.mjs +0 -427
  48. package/dist/chunks/tail-Bd7L940p.mjs +0 -355
  49. package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
  50. package/dist/workflows/cleanup.mjs +0 -36
  51. package/dist/workflows/feature.mjs +0 -1018
  52. package/dist/workflows/fix.mjs +0 -41
  53. package/dist/workflows/review.mjs +0 -45
  54. package/dist/workflows/rollout.mjs +0 -48
  55. package/dist/workflows.mjs +0 -40651
  56. package/src/install/ConfigScaffold.ts +0 -22
  57. package/src/workflows/cleanup/cleanup-path.feature +0 -94
  58. package/src/workflows/cleanup/cleanup.feature +0 -315
  59. package/src/workflows/cleanup/cleanup.test.ts +0 -276
  60. package/src/workflows/cleanup/cleanup.ts +0 -52
  61. package/src/workflows/cleanup/index.ts +0 -6
  62. package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
  63. package/src/workflows/feature/acceptance-criteria.feature +0 -389
  64. package/src/workflows/feature/criteria-coverage.feature +0 -102
  65. package/src/workflows/feature/criteria.test.ts +0 -147
  66. package/src/workflows/feature/criteria.ts +0 -267
  67. package/src/workflows/feature/fast-path.feature +0 -93
  68. package/src/workflows/feature/feature.test.ts +0 -338
  69. package/src/workflows/feature/feature.ts +0 -156
  70. package/src/workflows/feature/green.ts +0 -19
  71. package/src/workflows/feature/index.ts +0 -18
  72. package/src/workflows/feature/lens-models.feature +0 -69
  73. package/src/workflows/feature/lens-review.feature +0 -566
  74. package/src/workflows/feature/naive-acceptance.feature +0 -147
  75. package/src/workflows/feature/naive-test.feature +0 -263
  76. package/src/workflows/feature/one-step-land.feature +0 -93
  77. package/src/workflows/feature/planning.ts +0 -41
  78. package/src/workflows/feature/refactor-absorbs.feature +0 -131
  79. package/src/workflows/feature/refactor-first.feature +0 -158
  80. package/src/workflows/feature/refactor-lens-models.feature +0 -79
  81. package/src/workflows/feature/refactor-rebuild.feature +0 -273
  82. package/src/workflows/feature/refactor-review.feature +0 -1528
  83. package/src/workflows/feature/refactor.test.ts +0 -1220
  84. package/src/workflows/feature/refactor.ts +0 -368
  85. package/src/workflows/feature/release-flag.feature +0 -167
  86. package/src/workflows/feature/release-flag.test.ts +0 -49
  87. package/src/workflows/feature/release-flag.ts +0 -46
  88. package/src/workflows/feature/review-gate.feature +0 -241
  89. package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
  90. package/src/workflows/feature/risk-tier.feature +0 -351
  91. package/src/workflows/feature/seam-rejection.feature +0 -146
  92. package/src/workflows/feature/seam.feature +0 -297
  93. package/src/workflows/feature/seam.test.ts +0 -527
  94. package/src/workflows/feature/seam.ts +0 -426
  95. package/src/workflows/feature/standards.feature +0 -354
  96. package/src/workflows/feature/steps.test.ts +0 -65
  97. package/src/workflows/feature/steps.ts +0 -45
  98. package/src/workflows/feature/tier-pin.feature +0 -66
  99. package/src/workflows/feature/tier.test.ts +0 -166
  100. package/src/workflows/feature/tier.ts +0 -56
  101. package/src/workflows/fix/fix-lens-origin.feature +0 -114
  102. package/src/workflows/fix/fix-quality.feature +0 -212
  103. package/src/workflows/fix/fix.feature +0 -79
  104. package/src/workflows/fix/fix.test.ts +0 -74
  105. package/src/workflows/fix/fix.ts +0 -52
  106. package/src/workflows/fix/index.ts +0 -6
  107. package/src/workflows/index.ts +0 -47
  108. package/src/workflows/review/index.ts +0 -6
  109. package/src/workflows/review/review-door.feature +0 -199
  110. package/src/workflows/review/review.test.ts +0 -27
  111. package/src/workflows/review/review.ts +0 -60
  112. package/src/workflows/rollout/index.ts +0 -6
  113. package/src/workflows/rollout/rollout.feature +0 -510
  114. package/src/workflows/rollout/rollout.test.ts +0 -263
  115. package/src/workflows/rollout/rollout.ts +0 -82
  116. package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
  117. package/src/workflows/shared/access.test.ts +0 -62
  118. package/src/workflows/shared/access.ts +0 -24
  119. package/src/workflows/shared/config.fixture.ts +0 -12
  120. package/src/workflows/shared/fences.ts +0 -24
  121. package/src/workflows/shared/health.test.ts +0 -148
  122. package/src/workflows/shared/health.ts +0 -191
  123. package/src/workflows/shared/learn.test.ts +0 -140
  124. package/src/workflows/shared/learn.ts +0 -201
  125. package/src/workflows/shared/lenses.test.ts +0 -39
  126. package/src/workflows/shared/lenses.ts +0 -294
  127. package/src/workflows/shared/options.test.ts +0 -157
  128. package/src/workflows/shared/options.ts +0 -92
  129. package/src/workflows/shared/progress.feature +0 -124
  130. package/src/workflows/shared/prose.ts +0 -263
  131. package/src/workflows/shared/recheck.feature +0 -60
  132. package/src/workflows/shared/registry.test.ts +0 -345
  133. package/src/workflows/shared/registry.ts +0 -183
  134. package/src/workflows/shared/reserved.ts +0 -11
  135. package/src/workflows/shared/review.test.ts +0 -1062
  136. package/src/workflows/shared/review.ts +0 -446
  137. package/src/workflows/shared/skills.test.ts +0 -121
  138. package/src/workflows/shared/skills.ts +0 -49
  139. package/src/workflows/shared/standards.test.ts +0 -90
  140. package/src/workflows/shared/standards.ts +0 -156
  141. package/src/workflows/shared/steps.test.ts +0 -219
  142. package/src/workflows/shared/steps.ts +0 -201
  143. package/src/workflows/shared/tail.test.ts +0 -454
  144. package/src/workflows/shared/tail.ts +0 -264
  145. package/src/workflows/shared/text.fixture.ts +0 -128
  146. package/src/workflows/shared/text.test.ts +0 -1430
  147. package/src/workflows/shared/text.ts +0 -1700
  148. package/src/workflows/shared/threads.test.ts +0 -129
  149. package/src/workflows/shared/vars.ts +0 -47
  150. package/src/workflows/shared/workflows.steps.ts +0 -54
@@ -20888,12 +20888,7 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
20888
20888
  });
20889
20889
  //#endregion
20890
20890
  //#region src/flags/index.ts
20891
- const FLAGS$1 = [{
20892
- name: "risk_tier",
20893
- owner: "pmelab",
20894
- expiry: "2027-01-15",
20895
- kind: "release"
20896
- }];
20891
+ const FLAGS$1 = [];
20897
20892
  const BOOL = {
20898
20893
  "0": false,
20899
20894
  false: false,
@@ -20935,6 +20930,7 @@ const findPackageJson = (fromUrl) => {
20935
20930
  const GTD_VERSION = _require(findPackageJson(import.meta.url)).version;
20936
20931
  //#endregion
20937
20932
  //#region src/install/Install.ts
20933
+ const WORKFLOWS_REPO = "https://github.com/pmelab/gtd-workflows";
20938
20934
  const MINIMAL_DRIVER = `#!/usr/bin/env sh
20939
20935
  set -eu
20940
20936
 
@@ -21016,7 +21012,7 @@ while :; do
21016
21012
  system="$(gtd next --json=system)"
21017
21013
  validate="$(gtd next --json=validate)"
21018
21014
  log="$(gtd next --json=log)"
21019
- # \`model\` is a hint, not a model name — the bundled workflow ships
21015
+ # \`model\` is a hint, not a model name — gtd-workflows' default ships
21020
21016
  # \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
21021
21017
  # from your environment; unset, the hint is dropped and the agent runs
21022
21018
  # its own default. Any other value is a real model name.
@@ -21338,14 +21334,37 @@ follow in the same numbered list.
21338
21334
  existing config, the project's own README/docs) and ask the user what
21339
21335
  they're trying to accomplish with gtd here — don't assume a fresh
21340
21336
  install or silently start a process on their behalf.
21341
- 2. **Make sure a \`gtd.config.ts\` governs the repository.** Run at the
21342
- repository root, \`gtd install\` writes one importing gtd's shipped
21343
- workflows when none exists yet (its own message says so). Repository
21344
- settings — steering-file modes, \`gtd ui\`, per-scope skills and access —
21345
- go in its \`config\` export; workflow settings are options of the
21346
- workflow factory it exports.
21347
- 3. **Commit the config before the first drive.** \`gtd install\` leaves the
21348
- file it writes uncommitted on purpose — an uncommitted config is a
21337
+ 2. **Make sure a \`gtd.config.ts\` governs the repository.** gtd ships no
21338
+ workflow: the config imports the workflows it runs. Ask the user which
21339
+ way they want:
21340
+ - **A workflow from ${WORKFLOWS_REPO}.** Its \`default\` is a feature →
21341
+ review → fix workflow. It is used from a checkout pinned on this
21342
+ machine:
21343
+
21344
+ \`\`\`sh
21345
+ git clone ${WORKFLOWS_REPO} ~/.local/share/gtd/workflows
21346
+ ~/.local/share/gtd/workflows/bin/gtd-wf pin origin/main # or a tag
21347
+ ~/.local/share/gtd/workflows/bin/gtd-wf config default > gtd.config.ts
21348
+ \`\`\`
21349
+
21350
+ \`pin\` checks the ref out and bundles each workflow; \`config\` prints a
21351
+ config importing the bundle by its ABSOLUTE path. Tell the user what
21352
+ that means: the committed config works only where the checkout sits at
21353
+ that same path, so a teammate or CI needs their own checkout there (or
21354
+ their own config). If a checkout exists already, \`gtd-wf status\`
21355
+ shows its pin — reuse it rather than cloning again. Read the
21356
+ repository's README for the workflows it offers and their options.
21357
+ - **Their own workflow.** A workflow is TypeScript the config exports,
21358
+ written against \`@pmelab/gtd/flows\` — see
21359
+ https://github.com/pmelab/gtd/blob/main/docs/configuration.md, and
21360
+ https://github.com/pmelab/gtd/blob/main/docs/testing.md to test it. A
21361
+ copy of a gtd-workflows workflow (\`gtd-wf new <name> --from default\`
21362
+ in a clone of it) is a good start.
21363
+
21364
+ Repository settings — steering-file modes, \`gtd ui\`, per-scope skills
21365
+ and access — go in the config's \`config\` export; workflow settings are
21366
+ options of the workflow factory it exports.
21367
+ 3. **Commit the config before the first drive.** An uncommitted config is a
21349
21368
  pending change, so starting a process without committing it first turns
21350
21369
  the config into the very first thing the process plans against. Commit
21351
21370
  it, THEN start driving.
@@ -21371,7 +21390,8 @@ follow in the same numbered list.
21371
21390
  from every prompt line and writing no model exports, so every turn runs
21372
21391
  on the chosen CLI's own default tier.
21373
21392
  6. **If model hints are honored, resolve them to real model identifiers.**
21374
- The bundled workflow ships \`plannerModel: smart\` and \`coderModel: base\`.
21393
+ A workflow may declare model hints — gtd-workflows' \`default\` ships
21394
+ \`plannerModel: smart\` and \`coderModel: base\`.
21375
21395
  **Those are opaque hints, not model names** — gtd never interprets them,
21376
21396
  and passing \`--model smart\` to a real CLI fails on the first prompt
21377
21397
  beat. Derive the chosen CLI's own available models at install time — its
@@ -21468,11 +21488,8 @@ hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
21468
21488
  rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
21469
21489
  straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
21470
21490
  \`gtd doors\` lists the doors a repository offers (those of the workflows its
21471
- config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
21472
- \`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
21473
- (remove it), which a config adds by exporting \`rollout\` from
21474
- \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
21475
- \`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
21491
+ config exports — gtd-workflows' \`default\` brings \`fix\` and
21492
+ \`review [base]\` — plus any the config declares). Refuses with
21476
21493
  \`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
21477
21494
  missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
21478
21495
 
@@ -21587,28 +21604,6 @@ Any runtime works: the contract is \`--json\`/\`--json=<path>\` in, subprocesses
21587
21604
  out.
21588
21605
  `;
21589
21606
  const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + DRIVER_OBLIGATIONS + RECOVERY + commandSuite() + EDITOR_INTEGRATION + PREREQUISITES;
21590
- //#endregion
21591
- //#region src/install/ConfigScaffold.ts
21592
- const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature, review and fix.
21593
- // Every exported function is startable with \`gtd --workflow <name>\`; the
21594
- // default export is what a change in the tree starts. Each workflow brings
21595
- // its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
21596
- // from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
21597
- // \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
21598
- // \`gtd door cleanup <flag>\`.
21599
- export {
21600
- feature as default,
21601
- feature,
21602
- defaults,
21603
- envDefaults,
21604
- summary,
21605
- steering,
21606
- skills,
21607
- access,
21608
- } from "@pmelab/gtd/workflows/feature"
21609
- export { review } from "@pmelab/gtd/workflows/review"
21610
- export { fix } from "@pmelab/gtd/workflows/fix"
21611
- `;
21612
21607
  var FlowsProtocolError = class extends Error {
21613
21608
  constructor(engine, flows) {
21614
21609
  super(`gtd: the workflow speaks flows protocol ${flows}, but the engine installed protocol ${engine} — ${engine < flows ? "upgrade the gtd that runs it" : "upgrade the @pmelab/gtd the workflow imports"}`);
@@ -22073,7 +22068,7 @@ const defineWorkflow = (schema, build) => {
22073
22068
  options
22074
22069
  },
22075
22070
  then: () => {
22076
- throw new Error("a configured workflow is not a flow — compose fixFlow/reviewFlow instead");
22071
+ throw new Error("a configured workflow is not a flow — await the flow functions it is built from instead");
22077
22072
  }
22078
22073
  });
22079
22074
  return Object.assign(factory, { [FACTORY]: internals });
@@ -27856,7 +27851,7 @@ function tokenizeTitleBefore(effects, ok, nok) {
27856
27851
  return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
27857
27852
  }
27858
27853
  }
27859
- var definition, titleBefore;
27854
+ var definition$1, titleBefore;
27860
27855
  var init_definition = __esmMin((() => {
27861
27856
  init_micromark_factory_destination();
27862
27857
  init_micromark_factory_label();
@@ -27865,7 +27860,7 @@ var init_definition = __esmMin((() => {
27865
27860
  init_micromark_factory_whitespace();
27866
27861
  init_micromark_util_character();
27867
27862
  init_micromark_util_normalize_identifier();
27868
- definition = {
27863
+ definition$1 = {
27869
27864
  name: "definition",
27870
27865
  tokenize: tokenizeDefinition
27871
27866
  };
@@ -30853,7 +30848,7 @@ var init_constructs = __esmMin((() => {
30853
30848
  [57]: list,
30854
30849
  [62]: blockQuote
30855
30850
  };
30856
- contentInitial = { [91]: definition };
30851
+ contentInitial = { [91]: definition$1 };
30857
30852
  flowInitial = {
30858
30853
  [-2]: codeIndented,
30859
30854
  [-1]: codeIndented,
@@ -38001,7 +37996,7 @@ const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
38001
37996
  * `gtd judge answer` (the only other caller of `planLanding`/`landFields`)
38002
37997
  * passes its own two-word name so the hint stays runnable verbatim.
38003
37998
  */
38004
- const renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(run \`gtd ${command} --json=script | sh\` to get the landing script)\n` : noopText(fields.state);
37999
+ const renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(to land it: s=$(gtd ${command} --json=script) && sh -c "$s")\n` : noopText(fields.state);
38005
38000
  //#endregion
38006
38001
  //#region src/wire/Judge.ts
38007
38002
  const judgeJson = (state, questions) => JSON.stringify({
@@ -38953,13 +38948,35 @@ const resolveVars = (workflowVars, entryVars, env) => {
38953
38948
  }
38954
38949
  return merged;
38955
38950
  };
38951
+ const FLAG_VALUES = {
38952
+ "0": "0",
38953
+ false: "0",
38954
+ "1": "1",
38955
+ true: "1"
38956
+ };
38957
+ const flagVariable = (setting) => `GTD_FLAG_${setting.slice(5).toUpperCase()}`;
38958
+ /** The `flag_<name>` settings a workflow declares that gtd's own flags lack: the workflow's own release flags. */
38959
+ const ownFlags = (workflowVars, flags) => Object.keys(workflowVars).filter((name) => name.startsWith("flag_") && !Object.hasOwn(flags, name));
38960
+ /** Why a workflow's own release flag cannot start, if one is set to anything but 1, 0, true or false. */
38961
+ const ownFlagRefusal = (workflowVars, flags, env) => {
38962
+ const bad = ownFlags(workflowVars, flags).find((name) => {
38963
+ const raw = env[flagVariable(name)] ?? "";
38964
+ return raw !== "" && !Object.hasOwn(FLAG_VALUES, raw.toLowerCase());
38965
+ });
38966
+ return bad === void 0 ? void 0 : `${flagVariable(bad)} must be 1, 0, true or false`;
38967
+ };
38956
38968
  /**
38957
38969
  * `resolveVars` for a process being STARTED: `flags` land between the workflow
38958
- * defaults and `--var`, for already-declared names only. A running process never
38959
- * calls this — it replays its pinned record.
38970
+ * defaults and `--var`, for already-declared names only, and so does
38971
+ * `GTD_FLAG_<NAME>` for a `flag_<name>` the workflow declares itself. A running
38972
+ * process never calls this — it replays its pinned record.
38960
38973
  */
38961
38974
  const resolveStartVars = (workflowVars, flags, entryVars, env) => {
38962
38975
  const declared = Object.fromEntries(Object.entries(flags).filter(([name]) => Object.hasOwn(workflowVars, name)));
38976
+ for (const name of ownFlags(workflowVars, flags)) {
38977
+ const value = FLAG_VALUES[(env[flagVariable(name)] ?? "").toLowerCase()];
38978
+ if (value !== void 0) declared[name] = value;
38979
+ }
38963
38980
  return resolveVars({
38964
38981
  ...workflowVars,
38965
38982
  ...declared
@@ -39123,7 +39140,7 @@ const dedupeDiagnostics = (diagnostics) => {
39123
39140
  //#endregion
39124
39141
  //#region src/workflow/load.ts
39125
39142
  init_esm();
39126
- const configError = (diagnostics) => new GtdError(`gtd config:\n${diagnostics.filter((d) => d.severity === "error").map((d) => ` - ${formatDiagnostic(d)}`).join("\n")}`);
39143
+ const configError = (diagnostics) => new GtdError(`gtd: config:\n${diagnostics.filter((d) => d.severity === "error").map((d) => ` - ${formatDiagnostic(d)}`).join("\n")}`);
39127
39144
  const failOnErrors = (diagnostics) => diagnostics.some((d) => d.severity === "error") ? fail$7(configError(diagnostics)) : _void;
39128
39145
  const MIGRATION_DOC = "https://github.com/pmelab/gtd/blob/main/docs/configuration.md#migrating-from-gtdrc";
39129
39146
  /**
@@ -39139,7 +39156,7 @@ const refuseLegacyRc = gen(function* () {
39139
39156
  const found = files.filter((f) => f.owned).map((f) => f.path);
39140
39157
  if (found.length === 0) return;
39141
39158
  return yield* fail$7(new GtdError([
39142
- "gtd config:",
39159
+ "gtd: config:",
39143
39160
  ...found.map((file) => ` - ${file}: no longer read — delete it`),
39144
39161
  " gtd 23 removed the .gtdrc files; move each key they set:",
39145
39162
  " vars → an option of the workflow factory in gtd.config.ts, or its `defaults` export",
@@ -39163,7 +39180,7 @@ const load = gen(function* () {
39163
39180
  const missing = {
39164
39181
  severity: "error",
39165
39182
  path: [],
39166
- message: `no workflows configured: no ${WORKFLOW_MODULE} here or above — run \`gtd install\` to scaffold one`,
39183
+ message: `no workflows configured: no ${WORKFLOW_MODULE} here or above — gtd ships no workflow; \`gtd install\` says where to get one`,
39167
39184
  origin: host.root
39168
39185
  };
39169
39186
  return yield* fail$7(configError([missing]));
@@ -39175,7 +39192,7 @@ const load = gen(function* () {
39175
39192
  });
39176
39193
  const loaded = yield* try_({
39177
39194
  try: () => loadWorkflows(module, shipped),
39178
- catch: (e) => new GtdError(`gtd config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
39195
+ catch: (e) => new GtdError(`gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
39179
39196
  });
39180
39197
  const compiled = loaded.config;
39181
39198
  const diagnostics = dedupeDiagnostics(sortDiagnostics([...compiled.diagnostics, ...loaded.diagnostics]));
@@ -39680,7 +39697,7 @@ const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => r
39680
39697
  })), (outcome) => {
39681
39698
  if (outcome.kind === "rest") return succeed$3(outcome.rest.name);
39682
39699
  const problem = outcome.kind === "failed" || outcome.kind === "refused" ? outcome.message.replace(/^gtd: /, "") : "the default entry must begin at a step — that step is where a finished process waits";
39683
- return fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: ${problem}`));
39700
+ return fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: ${problem}`));
39684
39701
  });
39685
39702
  /**
39686
39703
  * The default entry's first step — where a finished process waits — found the
@@ -39696,13 +39713,13 @@ const firstStep = (loaded, head) => gen(function* () {
39696
39713
  const env = watchedEmptySettings();
39697
39714
  const outcome = yield* either(replayToFirstStep(loaded, vars.settings, env.settings, empty.tree));
39698
39715
  const reads = [...settingReads("process", vars), ...settingReads("environment", env)];
39699
- if (reads.length > 0) return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`));
39716
+ if (reads.length > 0) return yield* fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`));
39700
39717
  if (outcome._tag === "Left") return yield* fail$7(outcome.left);
39701
39718
  const name = outcome.right;
39702
39719
  if (!empty.read()) return name;
39703
39720
  const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
39704
39721
  if (atHead === name) return name;
39705
- return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`));
39722
+ return yield* fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`));
39706
39723
  });
39707
39724
  var ConfigService = class ConfigService extends Tag("ConfigService")() {
39708
39725
  static Live = succeed$2(ConfigService, { load });
@@ -39710,7 +39727,7 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
39710
39727
  //#endregion
39711
39728
  //#region src/workflow/skills.ts
39712
39729
  init_esm();
39713
- const fail$4 = (origin, message) => new GtdError(`gtd config:\n - ${origin}: ${message}`);
39730
+ const fail$4 = (origin, message) => new GtdError(`gtd: config:\n - ${origin}: ${message}`);
39714
39731
  const checkShape = (value) => {
39715
39732
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("skills export must return an object of scope -> skill names");
39716
39733
  for (const [scope, names] of Object.entries(value)) if (!Array.isArray(names) || names.some((n) => typeof n !== "string")) throw new Error(`skills export: "${scope}" must be an array of skill names`);
@@ -39919,7 +39936,11 @@ const planStart = (current, actor, start) => gen(function* () {
39919
39936
  const overrideRefusal = yield* checkOverrides(commandLabel, varOverrides, Object.keys(workflow.vars), Object.keys(workflow.env));
39920
39937
  if (overrideRefusal !== void 0) return overrideRefusal;
39921
39938
  const baseOf = workflow.def.base;
39922
- const vars = resolveStartVars(workflow.vars, yield* StartFlags, varOverrides, (yield* Host).env);
39939
+ const flags = yield* StartFlags;
39940
+ const hostEnv = (yield* Host).env;
39941
+ const badFlag = ownFlagRefusal(workflow.vars, flags, hostEnv);
39942
+ if (badFlag !== void 0) return refusal(`${commandLabel}: ${badFlag}`);
39943
+ const vars = resolveStartVars(workflow.vars, flags, varOverrides, hostEnv);
39923
39944
  const multiline = multilineSetting(vars);
39924
39945
  if (multiline !== void 0) return refusal(`${commandLabel}: process setting "${multiline}" spans several lines — a process setting is pinned in a commit trailer and must be a single line`);
39925
39946
  const template = yield* try_({
@@ -40493,7 +40514,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
40493
40514
  const step = outcome.rest;
40494
40515
  yield* (yield* Narrator).narrate(`rest resolved: ${step.name} (awaits ${step.actor})`);
40495
40516
  const stepDef = stepDefOf(step);
40496
- if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$7(/* @__PURE__ */ new Error(`gtd config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
40517
+ if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$7(/* @__PURE__ */ new Error(`gtd: config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
40497
40518
  const changes = ref === void 0 ? (yield* pendingChanges(git, stepDef.approve === true)).map((e) => ({
40498
40519
  status: normalizeStatus(e.status),
40499
40520
  path: e.path
@@ -40782,11 +40803,11 @@ const historySteps = (run) => {
40782
40803
  };
40783
40804
  });
40784
40805
  };
40785
- const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd: warning: this history was recorded by ${recorded === void 0 ? "an unknown gtd version" : `gtd ${recorded}`} and is replayed by gtd ${GTD_VERSION} — a workflow imported from @pmelab/gtd/workflows may have changed since`;
40806
+ const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd: warning: this history was recorded by ${recorded === void 0 ? "an unknown gtd version" : `gtd ${recorded}`} and is replayed by gtd ${GTD_VERSION} — the engine the workflow runs on may have changed since`;
40786
40807
  /**
40787
40808
  * Replays the closed run at `tip` under today's config. The workflow is not
40788
- * fingerprinted for a version change: one imported from `@pmelab/gtd/workflows`
40789
- * moves with every upgrade, and the version warning is the only signal.
40809
+ * fingerprinted for a version change: the engine under it moves with every
40810
+ * upgrade, and the version warning is the only signal.
40790
40811
  */
40791
40812
  const historyTrail = (tip) => gen(function* () {
40792
40813
  const git = yield* GitService;
@@ -41033,7 +41054,7 @@ const emitScripts = (required = [], optional = []) => ({
41033
41054
  optional: assembleScript(optional)
41034
41055
  });
41035
41056
  /** Exported so `src/twins/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
41036
- const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn";
41057
+ const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — run it with `sh` to apply it";
41037
41058
  const PRESENTATION_ONLY_COMMENT = "# presentation only — safe to skip";
41038
41059
  const PRESENTATION_FAILURE_WARNING = "printf 'gtd: presentation-only follow-up failed — continuing\\n' >&2";
41039
41060
  /**
@@ -42015,9 +42036,8 @@ const selfGtd = () => {
42015
42036
  const runLspCommand = () => flatMap$4(ToolCommands, (tools) => tools.lsp(selfGtd()));
42016
42037
  /**
42017
42038
  * Prints the briefing, emitted by the binary itself so it's always current.
42018
- * At a repository root no `gtd.config.ts` governs yet, it also writes one
42019
- * importing gtd's shipped workflows, left uncommitted.
42020
- * Anywhere else it writes nothing.
42039
+ * gtd ships no workflow, so where no `gtd.config.ts` governs it says where to
42040
+ * get one; it writes nothing.
42021
42041
  */
42022
42042
  const runInstallCommand = (out) => gen(function* () {
42023
42043
  const briefing = renderBriefing();
@@ -42027,8 +42047,7 @@ const runInstallCommand = (out) => gen(function* () {
42027
42047
  if (topLevel._tag === "Left") return;
42028
42048
  if ((yield* host.realPath(topLevel.right)) !== (yield* host.realPath(host.root))) return;
42029
42049
  if ((yield* (yield* ConfigDiscovery).workflowModule(host.root, host.home)) !== void 0) return;
42030
- yield* (yield* Workspace).write(WORKFLOW_MODULE, CONFIG_SCAFFOLD);
42031
- yield* (yield* Narrator).warn(`gtd install: wrote ${WORKFLOW_MODULE}, importing gtd's shipped workflows — commit it before your first \`gtd land\``);
42050
+ yield* (yield* Narrator).warn(`gtd install: no ${WORKFLOW_MODULE} governs here, and gtd ships no workflow — get one from ${WORKFLOWS_REPO} or write your own (step 2 of the briefing above)`);
42032
42051
  });
42033
42052
  /**
42034
42053
  * Exactly one trailing newline — duplicated from `Cli.ts`'s own
@@ -42216,7 +42235,10 @@ const runLandCommand = (opts, json, out) => gen(function* () {
42216
42235
  const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => gen(function* () {
42217
42236
  const config = yield* (yield* ConfigService).load;
42218
42237
  const resolved = config.workflowNamed(workflow);
42219
- if (resolved === void 0) return yield* fail$7(new GtdUsageError(`gtd --workflow: unknown workflow "${workflow}" — startable: ${config.workflowNames.join(", ")}`));
42238
+ if (resolved === void 0) {
42239
+ const startable = config.workflowNames.length > 0 ? `startable: ${config.workflowNames.join(", ")}` : "this config exports no workflow besides its default, which an ordinary start runs";
42240
+ return yield* fail$7(new GtdUsageError(`gtd --workflow: unknown workflow "${workflow}" — ${startable}`));
42241
+ }
42220
42242
  const rest = yield* restAt(void 0, varOverrides);
42221
42243
  const started = yield* startRefusal(rest, resolved, varOverrides);
42222
42244
  const plan = yield* planStart({
@@ -42230,9 +42252,24 @@ const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => ge
42230
42252
  config: rest.config
42231
42253
  });
42232
42254
  if (plan.kind === "refusal") return yield* fail$7(new Error(plan.message));
42233
- out.write(landingScript(ScriptSurface.render(plan.steps)));
42255
+ out.write(listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
42234
42256
  });
42257
+ /**
42258
+ * A start commits the whole tree (`git add -A`), so its script says up front
42259
+ * which paths that is. Comment lines only: control characters are masked so a
42260
+ * path can never end the comment and become a command.
42261
+ */
42262
+ const listingCommitted = (script, changes) => {
42263
+ if (changes.length === 0) return script;
42264
+ const listing = ["# commits every change in the working tree (git add -A):", ...changes.map((change) => `# ${change.status} ${change.path.replace(/\p{Cc}/gu, "?")}`)].join("\n");
42265
+ const at = script.indexOf("\n\n");
42266
+ return at === -1 ? script : `${script.slice(0, at)}\n\n${listing}${script.slice(at)}`;
42267
+ };
42235
42268
  const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => arg.optional === true ? `[${arg.name}]` : `<${arg.name}>`)].join(" ");
42269
+ const noSuchDoor = (name, doors) => {
42270
+ const listing = doors.size > 0 ? `doors: ${[...doors].map(([n, d]) => doorSynopsis(n, d)).join(", ")}` : "this config has no doors";
42271
+ return new GtdUsageError(name === void 0 ? `gtd door: missing door name — ${listing}` : `gtd door: unknown door "${name}" — ${listing}`);
42272
+ };
42236
42273
  /**
42237
42274
  * `gtd door <name> [args…]`: `gtd --workflow` with the door's workflow and the
42238
42275
  * process settings its `vars` derive from the positional args. A wrong name or
@@ -42240,11 +42277,8 @@ const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => ar
42240
42277
  */
42241
42278
  const runDoorCommand = (name, args, out) => gen(function* () {
42242
42279
  const config = yield* (yield* ConfigService).load;
42243
- const door = config.doors.get(name);
42244
- if (door === void 0) {
42245
- const known = [...config.doors];
42246
- return yield* fail$7(new GtdUsageError(`gtd door: unknown door "${name}" — doors: ${known.map(([n, d]) => doorSynopsis(n, d)).join(", ")}`));
42247
- }
42280
+ const door = name === void 0 ? void 0 : config.doors.get(name);
42281
+ if (name === void 0 || door === void 0) return yield* fail$7(noSuchDoor(name, config.doors));
42248
42282
  const declared = door.args ?? [];
42249
42283
  const required = declared.filter((arg) => arg.optional !== true).length;
42250
42284
  if (args.length < required || args.length > declared.length) return yield* fail$7(new GtdUsageError(`gtd door ${name}: ${args.length < required ? "missing" : "too many"} arguments — usage: gtd door ${doorSynopsis(name, door)}`));
@@ -42262,6 +42296,7 @@ const runDoorCommand = (name, args, out) => gen(function* () {
42262
42296
  const runDoorsCommand = (json, out) => gen(function* () {
42263
42297
  const doors = [...(yield* (yield* ConfigService).load).doors];
42264
42298
  if (json.kind === "off") {
42299
+ if (doors.length === 0) yield* (yield* Narrator).warn("gtd doors: this config has no doors — a workflow factory's `doors` option or the config's `doors` export adds one");
42265
42300
  out.write(doors.map(([n, d]) => `${doorSynopsis(n, d)} → ${d.workflow}\n`).join(""));
42266
42301
  return;
42267
42302
  }
@@ -42409,6 +42444,13 @@ const runNextCommand = (json, out) => gen(function* () {
42409
42444
  else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
42410
42445
  else {
42411
42446
  const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file) : void 0;
42447
+ if (DEMAND_BRIEFING[fields.kind].suppressHeader) {
42448
+ const shown = renderBeatPlain({
42449
+ ...fields,
42450
+ kind: "message"
42451
+ });
42452
+ yield* narrator.warn(shown.slice(0, shown.indexOf("\n\n")));
42453
+ }
42412
42454
  out.write(renderBeatPlain(fields, validateCommand));
42413
42455
  }
42414
42456
  });
@@ -42564,12 +42606,13 @@ const runUiCliCommand = (command, out) => gen(function* () {
42564
42606
  * cwds (e.g. macOS /tmp → /private/tmp) match.
42565
42607
  */
42566
42608
  const assertRunningFromRepoRoot = (git, host) => gen(function* () {
42567
- const topLevel = yield* git.topLevel();
42568
- if ((yield* host.realPath(topLevel)) !== (yield* host.realPath(host.root))) return yield* fail$7(/* @__PURE__ */ new Error(`gtd must be run from the repository root (${topLevel}); the current directory is ${host.root}`));
42609
+ const topLevel = yield* git.topLevel().pipe(mapError$1((e) => /not a git repository/.test(e.message) ? /* @__PURE__ */ new Error("gtd: not a git repository — run gtd inside one (`git init` makes one)") : e));
42610
+ if ((yield* host.realPath(topLevel)) !== (yield* host.realPath(host.root))) return yield* fail$7(/* @__PURE__ */ new Error(`gtd: run gtd from the repository root — cd ${shellWord(topLevel)} (the current directory is ${host.root})`));
42569
42611
  });
42612
+ const shellWord = (path) => /^[\w@%+=:,./-]+$/.test(path) ? path : `'${path.replace(/'/g, "'\\''")}'`;
42570
42613
  /** A command that derives workflow state needs at least one commit too — a commitless repository has no HEAD to resolve a `Rest` at. */
42571
42614
  const assertRepositoryHasCommits = (git) => gen(function* () {
42572
- if (!(yield* git.hasCommits())) return yield* fail$7(/* @__PURE__ */ new Error("gtd requires a repository with at least one commit — make an initial commit, then run gtd again"));
42615
+ if (!(yield* git.hasCommits())) return yield* fail$7(/* @__PURE__ */ new Error("gtd: the repository has no commits yet — make an initial commit, then run gtd again"));
42573
42616
  });
42574
42617
  /**
42575
42618
  * What a command kind needs before it may run. Defined here, not in
@@ -42656,6 +42699,57 @@ const runCommand = (command, json, out) => {
42656
42699
  });
42657
42700
  };
42658
42701
  //#endregion
42702
+ //#region src/cli/Help.ts
42703
+ const HELP_WIDTH = 80;
42704
+ /**
42705
+ * Word-wraps prose to `width` columns, every line prefixed by `indent`.
42706
+ * Paragraphs are separated by a blank line; a paragraph whose lines start
42707
+ * with two spaces is an example and is kept verbatim (indented, not wrapped).
42708
+ */
42709
+ const wrapParagraph = (paragraph, width, indent) => {
42710
+ const lines = [];
42711
+ let line = "";
42712
+ for (const word of paragraph.match(/\S*`[^`]*`\S*|\S+/g) ?? []) if (line !== "" && indent.length + line.length + 1 + word.length > width) {
42713
+ lines.push(indent + line);
42714
+ line = word;
42715
+ } else line = line === "" ? word : `${line} ${word}`;
42716
+ if (line !== "") lines.push(indent + line);
42717
+ return lines;
42718
+ };
42719
+ const wrap = (text, width = HELP_WIDTH, indent = "") => text.split("\n\n").flatMap((paragraph, i) => [...i > 0 ? [""] : [], ...paragraph.startsWith(" ") ? paragraph.split("\n").map((l) => indent + l) : wrapParagraph(paragraph, width, indent)]);
42720
+ /** `term` and `description` side by side, the description wrapped into the column after `column`; a term too wide for it gets a line of its own. */
42721
+ const definition = (term, description, column) => {
42722
+ const cell = ` ${term}`;
42723
+ const body = wrap(description, HELP_WIDTH, " ".repeat(column));
42724
+ if (cell.length + 1 >= column) return [cell, ...body];
42725
+ const [first = "", ...rest] = body;
42726
+ return [cell.padEnd(column) + first.trimStart(), ...rest];
42727
+ };
42728
+ /** Optimal-string-alignment distance: Levenshtein plus adjacent transpositions, so `nxet` is one edit from `next`. */
42729
+ const distance = (a, b) => {
42730
+ const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
42731
+ for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
42732
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
42733
+ let best = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
42734
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) best = Math.min(best, d[i - 2][j - 2] + 1);
42735
+ d[i][j] = best;
42736
+ }
42737
+ return d[a.length][b.length];
42738
+ };
42739
+ /** The candidate closest to `word`, when it is a plausible typo of it: at most one edit per three characters, and at least one. */
42740
+ const closest = (word, candidates) => {
42741
+ const limit = Math.max(1, Math.floor(word.length / 3));
42742
+ let best;
42743
+ for (const name of candidates) {
42744
+ const d = distance(word, name);
42745
+ if (d <= limit && (best === void 0 || d < best.d)) best = {
42746
+ name,
42747
+ d
42748
+ };
42749
+ }
42750
+ return best?.name;
42751
+ };
42752
+ //#endregion
42659
42753
  //#region src/cli/Cli.ts
42660
42754
  init_esm();
42661
42755
  const nonNegativeNumber = (raw, flag) => {
@@ -42684,20 +42778,8 @@ const FLAGS = [
42684
42778
  decode: ([raw]) => right(raw ?? ""),
42685
42779
  scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
42686
42780
  valueHint: "<path>",
42687
- help: [
42688
- "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
42689
- "list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
42690
- "--json=<path> (a dotted key path into that document, e.g.",
42691
- "kind, content, session.id) prints just that value: a",
42692
- "scalar raw and unquoted, a boolean as true/false, a list",
42693
- "one JSON entry per line — a numeric segment (e.g.",
42694
- "skills.0) indexes into one entry of a list field instead.",
42695
- "An absent optional field prints nothing and exits 0 —",
42696
- "including when an earlier segment of <path> is itself",
42697
- "absent/null (e.g. session.id at a non-prompt rest), which",
42698
- "never counts as unknown; an unknown path is a usage error",
42699
- "(exit 2)."
42700
- ]
42781
+ help: "Print the JSON document, or with --json=<path> one field of it (e.g. --json=kind): a scalar bare, a list one entry per line, nothing for an absent field",
42782
+ helpFor: { strays: "Print one entry of the array (--json=0), nothing past its end" }
42701
42783
  },
42702
42784
  {
42703
42785
  name: "--port",
@@ -42710,11 +42792,7 @@ const FLAGS = [
42710
42792
  },
42711
42793
  scopeError: "gtd: --port is only valid for `gtd ui`",
42712
42794
  valueHint: "<n>",
42713
- help: [
42714
- "(gtd ui only) the tailscale serve port (default: the first",
42715
- "free of 8443, 10000, 443), or the bind port when --host",
42716
- "opts out of serve (default: a free port)"
42717
- ]
42795
+ help: "The tailscale serve port (default: the first free of 8443, 10000, 443), or with --host the port to bind (default: any free one)"
42718
42796
  },
42719
42797
  {
42720
42798
  name: "--host",
@@ -42724,11 +42802,7 @@ const FLAGS = [
42724
42802
  decode: ([raw]) => raw === void 0 || raw.trim() === "" || /[\r\n]/.test(raw) ? left("gtd: --host must be a non-empty, single-line value") : right(raw),
42725
42803
  scopeError: "gtd: --host is only valid for `gtd ui`",
42726
42804
  valueHint: "<addr>",
42727
- help: [
42728
- "(gtd ui only) opt out of the default tailscale serve front",
42729
- "door and bind this address directly instead, showing it in",
42730
- "the printed URL"
42731
- ]
42805
+ help: "Bind this address directly instead of publishing through tailscale serve"
42732
42806
  },
42733
42807
  {
42734
42808
  name: "--self-signed",
@@ -42738,7 +42812,7 @@ const FLAGS = [
42738
42812
  decode: () => right(true),
42739
42813
  scopeError: "gtd: --self-signed is only valid for `gtd ui`",
42740
42814
  valueHint: "",
42741
- help: ["(gtd ui only) generate a throwaway self-signed TLS", "certificate instead of the configured ui.cert/ui.key"]
42815
+ help: "Bind directly with a throwaway self-signed certificate instead of the configured ui.cert/ui.key"
42742
42816
  },
42743
42817
  {
42744
42818
  name: "--dev",
@@ -42748,7 +42822,7 @@ const FLAGS = [
42748
42822
  decode: () => right(true),
42749
42823
  scopeError: "gtd: --dev is only valid for `gtd ui`",
42750
42824
  valueHint: "",
42751
- help: ["(gtd ui only) run against local development sources", "instead of the packaged build"]
42825
+ help: "Serve local development sources instead of the packaged build"
42752
42826
  },
42753
42827
  {
42754
42828
  name: "--provider",
@@ -42758,11 +42832,7 @@ const FLAGS = [
42758
42832
  decode: ([raw]) => raw === "fixed" || raw === "jev" || raw === "llm" ? right(raw) : left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
42759
42833
  scopeError: "gtd: --provider is only valid for `gtd judge run`",
42760
42834
  valueHint: "<name>",
42761
- help: [
42762
- "(gtd judge run only) the answerer: fixed | jev | llm;",
42763
- "omitted: GTD_JUDGE_PROVIDER, then judge.json, then jev",
42764
- "when TYPESAFE_API_KEY is set, else llm"
42765
- ]
42835
+ help: "The answerer: fixed, jev or llm (default: $GTD_JUDGE_PROVIDER, then ~/.config/gtd/judge.json, then jev when $TYPESAFE_API_KEY is set, else llm)"
42766
42836
  },
42767
42837
  {
42768
42838
  name: "--answers",
@@ -42772,11 +42842,7 @@ const FLAGS = [
42772
42842
  decode: ([raw]) => raw === void 0 || raw === "" ? left("gtd: --answers must be a non-empty path") : right(raw),
42773
42843
  scopeError: "gtd: --answers is only valid for `gtd judge run`",
42774
42844
  valueHint: "<path>",
42775
- help: [
42776
- "(gtd judge run --provider fixed only) a JSON verdict array",
42777
- "[{ id, answer, p }]; else the GTD_JUDGE_ANSWERS env var",
42778
- "(inline JSON) is read"
42779
- ]
42845
+ help: "With --provider fixed: a JSON verdict file [{ id, answer, p }] (default: $GTD_JUDGE_ANSWERS, inline JSON)"
42780
42846
  },
42781
42847
  {
42782
42848
  name: "--cost",
@@ -42786,7 +42852,7 @@ const FLAGS = [
42786
42852
  decode: ([raw]) => nonNegativeNumber(raw ?? "", "--cost"),
42787
42853
  scopeError: "gtd: --cost is only valid for `gtd land` — a start is not a metered agent turn",
42788
42854
  valueHint: "<n>",
42789
- help: ["(gtd land only) record the invocation's token cost"]
42855
+ help: "Record the token cost of the invocation that just finished on the commit"
42790
42856
  },
42791
42857
  {
42792
42858
  name: "--model",
@@ -42796,11 +42862,11 @@ const FLAGS = [
42796
42862
  decode: ([raw]) => raw === void 0 || raw.trim() === "" || /[\r\n]/.test(raw) ? left("gtd: --model must be a non-empty, single-line value") : right(raw),
42797
42863
  scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
42798
42864
  valueHint: "<name>",
42799
- help: [
42800
- "(gtd land, with --cost) tag that cost's model",
42801
- "(gtd judge run, llm answerer) the claude model; omitted:",
42802
- "GTD_JUDGE_MODEL, then judge.json, then sonnet"
42803
- ]
42865
+ help: "",
42866
+ helpFor: {
42867
+ land: "With --cost: the model that cost was spent on",
42868
+ judgeRun: "The claude model the llm answerer uses (default: $GTD_JUDGE_MODEL, then judge.json, then sonnet)"
42869
+ }
42804
42870
  },
42805
42871
  {
42806
42872
  name: "--workflow",
@@ -42810,7 +42876,7 @@ const FLAGS = [
42810
42876
  decode: ([raw]) => right(raw ?? ""),
42811
42877
  scopeError: "gtd: --workflow is only valid with no other command — use the bare `gtd --workflow <name>` form; landing and starting are different verbs",
42812
42878
  valueHint: "<name>",
42813
- help: ["(with no command at all) start a brand new process on", "the named workflow — authenticated as human"]
42879
+ help: "Start a new process on the named workflow"
42814
42880
  },
42815
42881
  {
42816
42882
  name: "--var",
@@ -42841,14 +42907,11 @@ const FLAGS = [
42841
42907
  },
42842
42908
  scopeError: "gtd: --var requires --workflow (or `gtd history judges`)",
42843
42909
  valueHint: "<name>=<value>",
42844
- help: [
42845
- "(with --workflow; repeatable) pin a process setting for the",
42846
- "new process; the name must already be declared by the",
42847
- "workflow's defaults (environment settings are not",
42848
- "pinnable). (gtd history judges) re-decide past verdicts",
42849
- "under this threshold: judgeIdenticalMinP or",
42850
- "qualityTriageMinP"
42851
- ]
42910
+ help: "",
42911
+ helpFor: {
42912
+ start: "Pin one of the workflow's declared settings for the new process (repeatable)",
42913
+ historyJudges: "Re-decide every past verdict under this threshold: judgeIdenticalMinP or qualityTriageMinP (repeatable)"
42914
+ }
42852
42915
  },
42853
42916
  {
42854
42917
  name: "--jsonl",
@@ -42858,7 +42921,7 @@ const FLAGS = [
42858
42921
  decode: () => right(true),
42859
42922
  scopeError: "gtd: --jsonl is only valid for `gtd history judges`",
42860
42923
  valueHint: "",
42861
- help: ["(gtd history judges only) print one JSON line per verdict", "instead of the report"],
42924
+ help: "Print one JSON line per verdict instead of the report",
42862
42925
  conflicts: ["--json", "--var"]
42863
42926
  },
42864
42927
  {
@@ -42869,12 +42932,7 @@ const FLAGS = [
42869
42932
  decode: () => right(true),
42870
42933
  scopeError: "gtd: --open-questions is only valid for `gtd check`",
42871
42934
  valueHint: "",
42872
- help: [
42873
- "(gtd check only) ignore <mode>'s structural findings and",
42874
- "instead run the qa open-questions predicate over <file>,",
42875
- "printing each unanswered question one per line and exiting",
42876
- "non-zero when any remain"
42877
- ]
42935
+ help: "Instead of <mode>'s findings, list the unanswered questions in <file>; exits 1 when any remain"
42878
42936
  },
42879
42937
  {
42880
42938
  name: "--open-threads",
@@ -42884,15 +42942,7 @@ const FLAGS = [
42884
42942
  decode: () => right(true),
42885
42943
  scopeError: "gtd: --open-threads is only valid for `gtd check`",
42886
42944
  valueHint: "",
42887
- help: [
42888
- "(gtd check only) ignore <mode>'s structural findings and",
42889
- "instead list every open thread (a footnote whose last",
42890
- "entry is the agent's) over <file>, one per line with its",
42891
- "first H entry, exiting non-zero when any are open. With no",
42892
- "<mode>/<file>, lists code-comment threads in the files the",
42893
- "current process changed (read from its diff base, unlike",
42894
- "the rest of `gtd check`)"
42895
- ]
42945
+ help: "Instead of <mode>'s findings, list the open threads in <file>; exits 1 when any are open. Alone, lists the open code-comment threads in the files the current process changed"
42896
42946
  },
42897
42947
  {
42898
42948
  name: "--verbose",
@@ -42902,11 +42952,7 @@ const FLAGS = [
42902
42952
  decode: () => right(true),
42903
42953
  scopeError: "gtd: --verbose is valid for every command — this error is unreachable",
42904
42954
  valueHint: "",
42905
- help: [
42906
- "enable stderr narration for this invocation: which rest",
42907
- "resolved and how config resolved across layers. Aliased",
42908
- "to -v"
42909
- ]
42955
+ help: "Narrate on stderr what gtd resolved and where its config came from"
42910
42956
  }
42911
42957
  ];
42912
42958
  /**
@@ -42925,205 +42971,39 @@ const flagByToken = (token) => {
42925
42971
  return FLAGS.find((f) => f.name === bareName);
42926
42972
  };
42927
42973
  const COMMAND_ROWS = [
42928
- {
42929
- token: "land",
42930
- kind: "land",
42931
- arity: "none",
42932
- details: [
42933
- "Land whatever the tree now shows at the currently resolved",
42934
- "rest — a human capture, an agent/check turn, or an empty",
42935
- "attempt (a fruitless prompt turn). Pass",
42936
- "--cost=<n> (optionally --model=<name>) to record the",
42937
- "just-finished invocation's token cost and model on the",
42938
- "turn commit (summed into it.processCost/",
42939
- "processCostByModel). Plain (the default) prints one",
42940
- "human-readable sentence naming the commit subject (or a",
42941
- "no-op note) plus a pointer to `gtd land --json=script | sh`",
42942
- "to get the landing script — never the script itself.",
42943
- "--json emits script (the actual POSIX sh",
42944
- "a driver runs) alongside settled, idle, state (the",
42945
- "post-land target), subject, cost and model. Exits 0 on",
42946
- "success, 1 on any refusal — see the Exit codes section",
42947
- "below"
42948
- ]
42949
- },
42950
- {
42951
- token: "abandon",
42952
- kind: "abandon",
42953
- arity: "none",
42954
- details: [
42955
- "End the process currently underway without completing it:",
42956
- "rewind HEAD to the commit the process started from,",
42957
- "keeping everything it produced as uncommitted changes. A",
42958
- "no-op when no process is underway"
42959
- ]
42960
- },
42961
- {
42962
- token: "restore",
42963
- kind: "restore",
42964
- arity: "none",
42965
- details: [
42966
- "Hard-reset HEAD back to the tip retained by the last",
42967
- "abandon (refs/worktree/gtd/history), bringing back an",
42968
- "abandoned process's turns. Refuses on a dirty working",
42969
- "tree, when there is no retained history, or when HEAD has",
42970
- "advanced past the retained tip with commits that would be",
42971
- "lost"
42972
- ]
42973
- },
42974
42974
  {
42975
42975
  token: "next",
42976
42976
  kind: "next",
42977
42977
  arity: "none",
42978
- details: [
42979
- "Print the resolved rest's beat (no mutation, safe to",
42980
- "poll), in one of three encodings. Plain (the default): a",
42981
- "status summary, a blank line, then the step verbatim — at a",
42982
- "script/capture rest an instruction line ('Run this",
42983
- "script:' / 'The edit is already made — run `gtd land` to",
42984
- "land it.') precedes the status summary; at a prompt rest",
42985
- "it's the bare step (plus the self-validation instruction",
42986
- "when applicable) with no header at all, since those bytes",
42987
- "are the agent's own input. --json emits the one structured",
42988
- "surface gtd has: kind (capture|message|script|prompt|",
42989
- "stalled) selects what a driver does, content is what it",
42990
- "runs or shows, idle marks the workflow's initial state with",
42991
- "a clean tree, plus the prompt session, model, validate",
42992
- "script, log path, changes, next and the resting state's own",
42993
- "fields. --json=<path> (a dotted key path into that same",
42994
- "document, e.g. kind, content, session.id) prints just that",
42995
- "value instead of the whole document — see --json's own help",
42996
- "above. Exits 0 — see the Exit codes section below"
42997
- ]
42998
- },
42999
- {
43000
- token: "validate",
43001
- kind: "validate",
43002
- arity: "none",
43003
- details: [
43004
- "Print the script that formats (when declared) then",
43005
- "validates the resolved rest's steering file, using its",
43006
- "mode's commands (its file:/mode:), instead of running it —",
43007
- "a driver runs the script and reads the findings from its",
43008
- "own exit code/output. Always exits 0; prints \"nothing to",
43009
- "validate\" when the resolved rest declares nothing to run.",
43010
- "On a non-zero validate exit the emitted script prints a",
43011
- "ready-to-send fix prompt (instruction + findings) and",
43012
- "exits with the validator's own code"
43013
- ]
43014
- },
43015
- {
43016
- token: "lsp",
43017
- kind: "lsp",
43018
- arity: "none",
43019
- details: ["Start the LSP server for .gtd/ steering files (stdio)"]
43020
- },
43021
- {
43022
- token: "ui",
43023
- kind: "ui",
43024
- arity: "none",
43025
- details: [
43026
- "Expose gtd's web/phone client for THIS worktree — the",
43027
- "invoking directory, never a configured list of roots — and",
43028
- "refuses outside a repository like every other state command.",
43029
- "By default, publishes through `tailscale serve` (reachable",
43030
- "from anywhere on the tailnet, including over a DERP relay)",
43031
- "with tailscaled terminating TLS, walking 8443, then 10000,",
43032
- "then 443 until one publishes; falls back to binding a local",
43033
- "HTTPS server directly, never refusing, when none of the",
43034
- "three works. --host <addr> opts out of serve and binds that",
43035
- "address directly instead; --port <n> overrides the serve",
43036
- "port (default: the first free of 8443, 10000, 443), or the",
43037
- "bind port when --host is given (default: a free port);",
43038
- "--self-signed also opts out of serve, generating a",
43039
- "throwaway TLS certificate instead of the configured",
43040
- "ui.cert/ui.key; --dev runs against local development sources",
43041
- "instead of the packaged build"
43042
- ]
43043
- },
43044
- {
43045
- token: "check",
43046
- kind: "check",
43047
- arity: { names: ["mode", "file"] },
43048
- details: [
43049
- "Read <file> and run the built-in steering format named",
43050
- "<mode> (see `gtd validate`'s modes: qa, review, findings)",
43051
- "over its contents, printing each finding one per line and",
43052
- "exiting non-zero when there are any. Resolves no workflow",
43053
- "state and reads no config — standalone, runnable from any",
43054
- "directory with <mode>/<file> given explicitly. This is what a",
43055
- "workflow's emitted validation script invokes as a leaf step.",
43056
- "--open-questions runs the qa unanswered-questions predicate",
43057
- "instead (see --help); --open-threads lists open threads.",
43058
- "`gtd check --open-threads` alone lists open code-comment",
43059
- "threads (`// H: ...`) in the files the process changed;",
43060
- "it prints nothing when no process is underway."
43061
- ]
43062
- },
43063
- {
43064
- token: "uncheck",
43065
- kind: "uncheck",
43066
- arity: { name: "file" },
43067
- details: [
43068
- "Read <file> and reset every review-mode `- [x]`/`- [X]`",
43069
- "file-pointer box back to `- [ ]`, writing the result back",
43070
- "only when the bytes actually changed. Resolves no workflow",
43071
- "state and reads no config — standalone, runnable from any",
43072
- "directory with <file> given explicitly. This is what the",
43073
- "landing script runs ahead of the commit at the human review",
43074
- "gate, so no tick ever reaches a commit. Takes no <mode> —",
43075
- "it means review-mode file pointers and nothing else, never",
43076
- "qa-mode's answered-question boxes. A missing file writes",
43077
- "nothing and exits 0"
43078
- ]
43079
- },
43080
- {
43081
- token: "install",
43082
- kind: "install",
43083
- arity: "none",
43084
- details: [
43085
- "Print a complete, self-contained briefing that teaches an",
43086
- "agent (or a human) to build a gtd driver in any shell or",
43087
- "runtime — the self-serve version of",
43088
- "https://github.com/pmelab/gtd/blob/main/docs/driver.md's",
43089
- "'Writing your own driver'. At a repository root no",
43090
- "gtd.config.ts governs yet, it also writes one importing each",
43091
- "shipped workflow from @pmelab/gtd/workflows/<name>; anywhere",
43092
- "else it writes nothing."
43093
- ]
42978
+ group: "Core loop",
42979
+ summary: "Print what should happen next",
42980
+ details: `Print what should happen next in the process, changing nothing — safe to poll.
42981
+
42982
+ Plain output is for a person: a status header (the step, who acts, the pending changes, where landing them would go), then the step's content. At a prompt the header goes to stderr, so stdout is exactly the agent's input.
42983
+
42984
+ --json prints one JSON document for a driver: its kind (capture, message, script, prompt or stalled) says what to do, its content what to run or show. --json=<path> prints one field of it; an unknown path is a usage error (exit 2).`
43094
42985
  },
43095
42986
  {
43096
- token: "summary",
43097
- kind: "summary",
42987
+ token: "land",
42988
+ kind: "land",
43098
42989
  arity: "none",
43099
- details: [
43100
- "Print the prompt for an agent to write the process HEAD",
43101
- "closes or sits inside its own closing message — the entry",
43102
- "commit, each human-authored commit (a review round, an",
43103
- "answered question gate), the diff range to inspect, and",
43104
- "it.processCost/processCostByModel. Writes nothing: no git,",
43105
- "no state transition, no file, no session identity — the",
43106
- "driver pipes the output to a cold agent and does what it",
43107
- "wants with the result (a squash, an amend, a PR body).",
43108
- "Refuses (exit 1) when the workflow declares no summary:",
43109
- "template, or when the resolved run has no commits to name —",
43110
- "it is runnable any time before the next thing lands on the",
43111
- "branch"
43112
- ]
42990
+ group: "Core loop",
42991
+ summary: "Record what the tree now shows",
42992
+ details: `Record what the working tree now shows at the current step: a person's edit, an agent's or a check's turn, or an empty attempt when an agent changed nothing.
42993
+
42994
+ gtd writes nothing itself. Plain output names the commit it would make; --json=script prints the script that makes it:
42995
+
42996
+ s=$(gtd land --json=script) && sh -c "$s"
42997
+
42998
+ --json prints the whole document: the script, the step the process lands at, the commit subject, and the recorded cost and model. A refusal exits 1.`
43113
42999
  },
43114
43000
  {
43115
- token: "base",
43116
- kind: "base",
43001
+ token: "validate",
43002
+ kind: "validate",
43117
43003
  arity: "none",
43118
- details: [
43119
- "Print the review anchor hash — the diff base an external",
43120
- "tool (a diff, a PR tool, another agent) should point at —",
43121
- "bare, newline-terminated, and nothing else. Writes nothing:",
43122
- "no git, no state transition, no session identity. Before",
43123
- "the first review round it's the process's diff base;",
43124
- "afterward it's the most-recent review round's boundary.",
43125
- "Refuses (exit 1) when no process is underway."
43126
- ]
43004
+ group: "Core loop",
43005
+ summary: "Print the script that checks the steering file",
43006
+ details: `Print the script that formats, then validates, the current step's steering file with its mode's commands. gtd runs nothing: the script's exit code is the verdict, and on a failure it prints a ready-to-send fix prompt. Prints "nothing to validate" when the step declares no file to check.`
43127
43007
  },
43128
43008
  {
43129
43009
  token: "door",
@@ -43132,73 +43012,55 @@ const COMMAND_ROWS = [
43132
43012
  name: "name",
43133
43013
  variadic: "args"
43134
43014
  },
43135
- details: [
43136
- "Start a process through a named shortcut, mapping the",
43137
- "positional args to that workflow's process settings — the",
43138
- "same start `gtd --workflow` makes, printed the same way.",
43139
- "Each workflow the config exports brings its own doors (gtd's",
43140
- "`fix`, `review [base]`, `rollout <flag>` and",
43141
- "`cleanup <flag>`), plus the config's `doors` export. An",
43142
- "unknown door, or too few or too many args, is a usage error",
43143
- "(exit 2) naming the doors or the door's synopsis"
43144
- ]
43015
+ group: "Start and stop",
43016
+ summary: "Start a process through a named shortcut",
43017
+ details: `Start a process through a door: a named shortcut to a workflow that maps its positional args to the workflow's settings. It prints the same start script \`gtd --workflow\` does. Each workflow the config exports brings its own doors, plus the config's \`doors\` export.
43018
+
43019
+ With no <name>, an unknown one, or the wrong number of args, it lists the doors and exits 2.`
43145
43020
  },
43146
43021
  {
43147
43022
  token: "doors",
43148
43023
  kind: "doors",
43149
43024
  arity: "none",
43150
- details: [
43151
- "List every door, sorted: one `name [args] → workflow` line",
43152
- "each. --json prints one JSON array of { name, workflow,",
43153
- "args: [{ name, optional }] } instead; --json=<path> reads one",
43154
- "value off it. Writes nothing"
43155
- ]
43025
+ group: "Start and stop",
43026
+ summary: "List the doors",
43027
+ details: `List every door, sorted: one \`name [args] → workflow\` line each. --json prints a JSON array of { name, workflow, args: [{ name, optional }] }. Writes nothing.`
43156
43028
  },
43157
43029
  {
43158
- token: "judge",
43159
- kind: "judge",
43030
+ token: "abandon",
43031
+ kind: "abandon",
43160
43032
  arity: "none",
43161
- details: [
43162
- "Print the resolved rest's pending judgment — the prepared",
43163
- "state, its typed questions, and their criteria — the same",
43164
- "judge field `gtd next --json` already carries. Read-only:",
43165
- "resolves no session, writes nothing. Refuses (exit 1) when",
43166
- "the resolved rest declares no judge:"
43167
- ]
43033
+ group: "Start and stop",
43034
+ summary: "Stop the process, keeping its changes",
43035
+ details: `Stop the process underway without finishing it. The printed script moves HEAD back to the commit the process started from and keeps everything it produced as uncommitted changes; \`gtd restore\` undoes it. With no process underway the script does nothing.
43036
+
43037
+ s=$(gtd abandon) && sh -c "$s"`
43168
43038
  },
43169
43039
  {
43170
- token: "judge answer",
43171
- kind: "judgeAnswer",
43040
+ token: "restore",
43041
+ kind: "restore",
43172
43042
  arity: "none",
43173
- details: [
43174
- "Read a verdict off stdin — one { id, answer, p } entry per",
43175
- "question the pending judgment declared — and decode it",
43176
- "against an Effect Schema built from those same question",
43177
- "ids. Refuses (exit 1) when the resolved rest declares no",
43178
- "judge:. Exits 2 (a usage error, like an unknown --json",
43179
- "selector) when stdin isn't valid JSON or the verdict",
43180
- "doesn't decode against the pending question ids."
43181
- ]
43043
+ group: "Start and stop",
43044
+ summary: "Bring back the last abandoned process",
43045
+ details: `Bring back the process the last \`gtd abandon\` stopped: the printed script resets HEAD to that process's last commit. Refuses on a dirty working tree, when nothing was abandoned, or when commits made since would be lost.`
43182
43046
  },
43183
43047
  {
43184
- token: "judge run",
43185
- kind: "judgeRun",
43048
+ token: "summary",
43049
+ kind: "summary",
43186
43050
  arity: "none",
43187
- details: [
43188
- "Answer a judgment: read the `gtd judge --json` document off",
43189
- "stdin, write a verdict [{ id, answer, p }] on stdout — what",
43190
- "`gtd judge answer` decodes. --provider fixed answers from",
43191
- "--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
43192
- "does not cover are left out. --provider jev asks TypeSafe",
43193
- "(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
43194
- "on PATH (Claude Code only), model sonnet unless --model.",
43195
- "No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
43196
- "judge.json, else jev when TYPESAFE_API_KEY is set and no",
43197
- "model is given, else llm; --model likewise falls back to",
43198
- "GTD_JUDGE_MODEL, then the file's model. Needs no",
43199
- "repository. jev and llm exit 1 when they cannot answer",
43200
- "every question, with nothing on stdout"
43201
- ]
43051
+ group: "Inspect",
43052
+ summary: "Print the prompt for the closing message",
43053
+ details: `Print the prompt for an agent to write the closing message of the process HEAD closes or sits inside: its first commit, each commit a person made, the diff range to read, and its cost. Writes nothing — what to do with the answer (a squash message, a PR body) is the driver's call.
43054
+
43055
+ Refuses when the workflow declares no summary template, or when the process has no commits.`
43056
+ },
43057
+ {
43058
+ token: "base",
43059
+ kind: "base",
43060
+ arity: "none",
43061
+ group: "Inspect",
43062
+ summary: "Print the commit a review diff starts from",
43063
+ details: `Print the commit a review diff should start from, bare: the process's diff base before the first review round, the latest round's boundary after it. Writes nothing; refuses when no process is underway.`
43202
43064
  },
43203
43065
  {
43204
43066
  token: "strays",
@@ -43207,45 +43069,27 @@ const COMMAND_ROWS = [
43207
43069
  name: "range",
43208
43070
  variadic: "glob"
43209
43071
  },
43210
- details: [
43211
- "List the files <range> (<base>..<tip>, an empty side is HEAD)",
43212
- "touched in its committed net diff that match none of the",
43213
- "globs. Anchored at the repo root: * stays within one path",
43214
- "segment, ** crosses them, everything else is literal — quote",
43215
- "globs in the shell. stdout: a sorted JSON array, [] when",
43216
- "clean; stderr: a summary, plus one stray: line per path with",
43217
- "--verbose. --json=<n> picks one entry, nothing past the end;",
43218
- "a symlink is a path like any other, never followed; a",
43219
- "submodule counts as its path, whatever diff.ignoreSubmodules",
43220
- "or .gitmodules ignore say. Runs in any git repository, no",
43221
- "process needed. Exits 10 when",
43222
- "strays were found, 1 on an unknown revision, 2 on a bad range"
43223
- ]
43072
+ group: "Inspect",
43073
+ summary: "List changed files outside the globs",
43074
+ details: `List the files changed in <range> (<base>..<tip>, an empty side is HEAD) that match none of the globs. Globs are anchored at the repository root: * stays within one path segment, ** crosses them, everything else is literal — quote them in the shell.
43075
+
43076
+ Prints a sorted JSON array ([] when clean) and a summary on stderr; --verbose adds one stray: line per path. Runs in any git repository, no process needed. Exits 10 when strays were found, 1 on an unknown revision, 2 on a bad range.`
43224
43077
  },
43225
43078
  {
43226
43079
  token: "history list",
43227
43080
  kind: "historyList",
43228
43081
  arity: "none",
43229
- details: [
43230
- "List the finished processes kept behind refs/gtd/history/*,",
43231
- "newest first: entry, branch, tip, date and subject per line.",
43232
- "--json prints one array of { ref, entry, branch, tip, date,",
43233
- "subject }. Writes nothing and never replays; with no refs it",
43234
- "hints at `gtd history fetch | sh` and still exits 0"
43235
- ]
43082
+ group: "Inspect",
43083
+ summary: "List finished processes",
43084
+ details: `List the finished processes this clone keeps, newest first: entry, branch, tip, date and subject. --json prints an array of { ref, entry, branch, tip, date, subject }. Writes nothing; with none kept it suggests \`gtd history fetch\`.`
43236
43085
  },
43237
43086
  {
43238
43087
  token: "history show",
43239
43088
  kind: "historyShow",
43240
43089
  arity: { name: "ref" },
43241
- details: [
43242
- "Replay one kept process under today's config and print its",
43243
- "steps, judge verdicts and approvals. <ref> is a full ref, an",
43244
- "entry hash (or unique prefix) or a branch name. Refuses",
43245
- "(exit 1) when a config file differs from its content at the",
43246
- "ref's tip. A version mismatch only warns on stderr. --json",
43247
- "prints the trail as one document; writes nothing"
43248
- ]
43090
+ group: "Inspect",
43091
+ summary: "Replay one finished process",
43092
+ details: `Replay one finished process under today's config and print its steps, judge verdicts and approvals. <ref> is a full ref, an entry hash or a unique prefix of one, or a branch name. Refuses when a config file changed since; a different gtd version only warns. --json prints the trail as one document. Writes nothing.`
43249
43093
  },
43250
43094
  {
43251
43095
  token: "history metrics",
@@ -43254,18 +43098,9 @@ const COMMAND_ROWS = [
43254
43098
  name: "ref",
43255
43099
  many: true
43256
43100
  },
43257
- details: [
43258
- "Report how the kept processes went: builds whose naive pass",
43259
- "landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
43260
- "model and phase with the double work of a rebuild, quality-lap",
43261
- "findings per lens, and red checks that passed on their",
43262
- "re-check — per process and in total. Each <ref>",
43263
- "is read as by history show, but a branch name means every",
43264
- "process kept from it; none means every ref.",
43265
- "Reads commit subjects and trailers, never replays, so a",
43266
- "changed config is no refusal. --json prints { processes, total",
43267
- "} as one document; writes nothing"
43268
- ]
43101
+ group: "Inspect",
43102
+ summary: "Report cost and rework of finished processes",
43103
+ details: `Report how finished processes went: builds that landed first time or were rebuilt, cost by model and phase, review findings per lens, and red checks that passed when re-run — per process and in total. <ref> is read as by \`gtd history show\`, but a branch name means every process kept from it; no <ref> means all of them. Never replays, so a changed config is no reason to refuse. --json prints { processes, total }. Writes nothing.`
43269
43104
  },
43270
43105
  {
43271
43106
  token: "history judges",
@@ -43274,18 +43109,9 @@ const COMMAND_ROWS = [
43274
43109
  name: "ref",
43275
43110
  many: true
43276
43111
  },
43277
- details: [
43278
- "Calibrate the judges of the kept processes from their",
43279
- "recorded Gtd-Judge verdicts: per judge (retry, triage, clear)",
43280
- "and p bucket, how many verdicts later events overturned.",
43281
- "--var <name>=<value> re-decides every past verdict under",
43282
- "another threshold and prints what would have changed;",
43283
- "--jsonl prints one { process, judge, commit, step, question,",
43284
- "state, answer, p, outcome } line per verdict instead. <ref>",
43285
- "is read as by history metrics. Never replays and asks no",
43286
- "model. --json prints { processes, total, whatIf }; writes",
43287
- "nothing"
43288
- ]
43112
+ group: "Inspect",
43113
+ summary: "Calibrate the judges of finished processes",
43114
+ details: `Calibrate the judges of finished processes from their recorded verdicts: per judge and confidence bucket, how many verdicts later events overturned. <ref> is read as by \`gtd history metrics\`. Never replays and asks no model. --json prints { processes, total, whatIf }. Writes nothing.`
43289
43115
  },
43290
43116
  {
43291
43117
  token: "history fetch",
@@ -43294,69 +43120,205 @@ const COMMAND_ROWS = [
43294
43120
  name: "remote",
43295
43121
  optional: true
43296
43122
  },
43297
- details: [
43298
- "Print a script that fetches every refs/gtd/history/* from",
43299
- "<remote> (default origin) and reports how many arrived — run",
43300
- "it with `gtd history fetch | sh`. Never runs git itself"
43301
- ]
43123
+ group: "Inspect",
43124
+ summary: "Print a script that fetches finished processes",
43125
+ details: `Print a script that fetches every finished process kept on <remote> (default: origin) and reports how many arrived. gtd runs no git itself:
43126
+
43127
+ s=$(gtd history fetch) && sh -c "$s"`
43128
+ },
43129
+ {
43130
+ token: "ui",
43131
+ kind: "ui",
43132
+ arity: "none",
43133
+ group: "Inspect",
43134
+ summary: "Serve the web and phone client",
43135
+ details: `Serve gtd's web and phone client for this worktree. By default it publishes through \`tailscale serve\`, reachable from anywhere on the tailnet, on the first of ports 8443, 10000 and 443 that works; when none does it serves HTTPS locally instead of refusing.`
43136
+ },
43137
+ {
43138
+ token: "install",
43139
+ kind: "install",
43140
+ arity: "none",
43141
+ group: "Driver plumbing",
43142
+ summary: "Print a briefing for writing a driver",
43143
+ details: `Print a self-contained briefing that teaches an agent or a person to build a gtd driver in any shell or runtime, and where to get a workflow — gtd ships none. Writes nothing.`
43144
+ },
43145
+ {
43146
+ token: "judge",
43147
+ kind: "judge",
43148
+ arity: "none",
43149
+ group: "Driver plumbing",
43150
+ summary: "Print the pending judgment",
43151
+ details: `Print the current step's pending judgment: the prepared state, its questions and their criteria — the judge field \`gtd next --json\` carries. Writes nothing; refuses when the step declares no judge.`
43152
+ },
43153
+ {
43154
+ token: "judge answer",
43155
+ kind: "judgeAnswer",
43156
+ arity: "none",
43157
+ group: "Driver plumbing",
43158
+ summary: "Land a verdict read from stdin",
43159
+ details: `Read a verdict from stdin — one { id, answer, p } entry per pending question — and land it the way \`gtd land\` does: plain output names the commit, --json=script prints the script. Refuses when the step declares no judge; a verdict that is not JSON, or names a question that was not asked, is a usage error (exit 2).`
43160
+ },
43161
+ {
43162
+ token: "judge run",
43163
+ kind: "judgeRun",
43164
+ arity: "none",
43165
+ group: "Driver plumbing",
43166
+ summary: "Answer a judgment with a model",
43167
+ details: `Answer a judgment: read the \`gtd judge --json\` document from stdin and print the verdict [{ id, answer, p }] \`gtd judge answer\` reads. Needs no repository.
43168
+
43169
+ The fixed answerer reads --answers and leaves out the questions it does not cover; jev asks TypeSafe ($TYPESAFE_API_KEY); llm asks the \`claude\` CLI on PATH. jev and llm exit 1, printing nothing, when they cannot answer every question.`
43170
+ },
43171
+ {
43172
+ token: "check",
43173
+ kind: "check",
43174
+ arity: { names: ["mode", "file"] },
43175
+ group: "Driver plumbing",
43176
+ summary: "Check a steering file's format",
43177
+ details: `Run the steering format <mode> (qa, review or findings) over <file> and print each finding on a line of its own; exits 1 when there are any. Reads no workflow and runs from any directory — emitted validation scripts call it.`
43178
+ },
43179
+ {
43180
+ token: "uncheck",
43181
+ kind: "uncheck",
43182
+ arity: { name: "file" },
43183
+ group: "Driver plumbing",
43184
+ summary: "Untick a review file's boxes",
43185
+ details: `Reset every ticked review box (- [x]) in <file> to - [ ], writing the file only when it changes; a missing file writes nothing. Reads no workflow and runs from any directory — the landing script at a review gate calls it, so no tick is ever committed.`
43186
+ },
43187
+ {
43188
+ token: "lsp",
43189
+ kind: "lsp",
43190
+ arity: "none",
43191
+ group: "Driver plumbing",
43192
+ summary: "Start the steering-file language server",
43193
+ details: `Start the language server for .gtd/ steering files on stdio.`
43302
43194
  }
43303
43195
  ];
43304
43196
  const commandByToken = (token) => COMMAND_ROWS.find((r) => r.token === token);
43305
- const COLUMN = 19;
43306
- const renderBlock = (header, lines) => {
43307
- const [first, ...rest] = lines;
43308
- const headerCell = ` ${header}`;
43309
- return (headerCell.length < COLUMN ? `${headerCell.padEnd(COLUMN)}${first ?? ""}\n` : `${headerCell}\n${" ".repeat(COLUMN)}${first ?? ""}\n`) + rest.map((l) => `${" ".repeat(COLUMN)}${l}\n`).join("");
43310
- };
43311
- const START_SHORT_FORM = renderBlock("(no command) --workflow <name>", ["Starts a new process authenticated as human, e.g.", "'gtd --workflow <name>'"]);
43312
- const VERSION_BLOCK = renderBlock("version", ["Print version and exit"]);
43313
- const HELP_BLOCK = renderBlock("help", ["Print this help and exit"]);
43197
+ /** Every word that may follow `gtd`, for "did you mean". */
43198
+ const COMMAND_WORDS = [.../* @__PURE__ */ new Set([
43199
+ ...COMMAND_ROWS.map((r) => r.token.split(" ")[0]),
43200
+ "help",
43201
+ "version"
43202
+ ])];
43203
+ const TOP_COLUMN = 28;
43204
+ const OPTION_COLUMN = 22;
43205
+ const synopsis = (row) => {
43206
+ const { token, arity } = row;
43207
+ if (arity === "none") return token;
43208
+ if ("names" in arity) return `${token} <${arity.names[0]}> <${arity.names[1]}>`;
43209
+ if ("variadic" in arity) return `${token} <${arity.name}> [${arity.variadic}...]`;
43210
+ if ("optional" in arity) return `${token} [${arity.name}]`;
43211
+ if ("many" in arity) return `${token} [${arity.name}...]`;
43212
+ return `${token} <${arity.name}>`;
43213
+ };
43314
43214
  const SPACE_FORM_FLAGS = /* @__PURE__ */ new Set(["--workflow", "--var"]);
43315
43215
  const flagHeader = (row) => {
43316
43216
  if (row.valueHint === "") return row.name;
43217
+ if (row.arity === "optional") return `${row.name}[=${row.valueHint}]`;
43317
43218
  return SPACE_FORM_FLAGS.has(row.name) ? `${row.name} ${row.valueHint}` : `${row.name}=${row.valueHint}`;
43318
43219
  };
43220
+ const START_ROW = {
43221
+ term: "--workflow <name>",
43222
+ summary: "Start a process on a workflow; --var <name>=<value> pins one of its settings"
43223
+ };
43224
+ const GLOBAL_OPTIONS = [
43225
+ ["-v, --verbose", "Narrate on stderr what gtd resolved"],
43226
+ ["-h, --help", "Print help; gtd <command> --help for one command"],
43227
+ ["-V, --version", "Print the version"]
43228
+ ];
43229
+ const EXTRA_TOPICS = [[
43230
+ "help [command]",
43231
+ "Print help for gtd or one command",
43232
+ "Print gtd's overview, or with <command> that command's own help."
43233
+ ], [
43234
+ "version",
43235
+ "Print the version",
43236
+ "Print gtd's version."
43237
+ ]];
43238
+ const EXAMPLES = ` $ gtd --workflow <name> start a process
43239
+ $ gtd next see what happens next
43240
+ $ s=$(gtd land --json=script) && sh -c "$s" land the turn
43241
+ $ gtd help land one command's details`;
43242
+ const lines = (block) => block.map((l) => `${l}\n`).join("");
43243
+ const GROUPS = [
43244
+ "Core loop",
43245
+ "Start and stop",
43246
+ "Inspect",
43247
+ "Driver plumbing"
43248
+ ];
43319
43249
  const renderHelp = () => {
43320
- const commandBlocks = COMMAND_ROWS.map((row) => {
43321
- const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : "optional" in row.arity ? `${row.token} [${row.arity.name}]` : "many" in row.arity ? `${row.token} [${row.arity.name}...]` : `${row.token} <${row.arity.name}>`;
43322
- return renderBlock(header, row.details);
43323
- });
43324
- return `Usage: gtd [command] [options]\n\nCommands:\n${[
43325
- commandBlocks[0],
43326
- START_SHORT_FORM,
43327
- ...commandBlocks.slice(1),
43328
- VERSION_BLOCK,
43329
- HELP_BLOCK
43330
- ].join("")}\nOptions:\n${FLAGS.map((row) => renderBlock(flagHeader(row), row.help)).join("")}${renderBlock("--version, -V", ["Print version and exit"]) + renderBlock("--help, -h", ["Print this help and exit"])}`;
43250
+ const groups = GROUPS.map((group) => {
43251
+ const rows = COMMAND_ROWS.filter((r) => r.group === group).flatMap((r) => definition(synopsis(r), r.summary, TOP_COLUMN));
43252
+ const start = group === "Start and stop" ? definition(START_ROW.term, START_ROW.summary, TOP_COLUMN) : [];
43253
+ const extra = group === "Driver plumbing" ? EXTRA_TOPICS.flatMap(([term, summary]) => definition(term, summary, TOP_COLUMN)) : [];
43254
+ return `${group}:\n${lines([
43255
+ ...start,
43256
+ ...rows,
43257
+ ...extra
43258
+ ])}`;
43259
+ });
43260
+ return "Usage: gtd <command> [options]\n\n" + lines(wrap("gtd reads a repository's git state and prints what should happen next in its workflow. It never acts on it: a driver, or you, runs what it prints.")) + "\n" + groups.join("\n") + "\nOptions:\n" + lines(GLOBAL_OPTIONS.flatMap(([term, text]) => definition(term, text, TOP_COLUMN))) + `\nExamples:\n${EXAMPLES}\n\n` + lines(wrap("Run `gtd help <command>` for a command's details and options, and `gtd install` to learn how to drive gtd."));
43261
+ };
43262
+ /** Shown on stderr above a bare `gtd`'s usage error — the overview stays one `gtd help` away. */
43263
+ const SHORT_USAGE = `Usage: gtd <command> [options]
43264
+
43265
+ gtd --workflow <name> start a process
43266
+ gtd next print what should happen next
43267
+ gtd land record what the tree now shows
43268
+
43269
+ `;
43270
+ const renderCommandHelp = (row) => {
43271
+ const options = FLAGS.filter((f) => f.name !== "--verbose" && f.scope(row.kind)).flatMap((f) => definition(flagHeader(f), f.helpFor?.[row.kind] ?? f.help, OPTION_COLUMN));
43272
+ const global = definition("-v, --verbose", "Narrate on stderr what gtd resolved", OPTION_COLUMN);
43273
+ return `Usage: gtd ${synopsis(row)}${options.length > 0 ? " [options]" : ""}\n\n` + lines(wrap(row.details)) + `\nOptions:\n${lines([...options, ...global])}`;
43274
+ };
43275
+ /** `gtd help <topic…>`'s text: one command's help, every `history` subcommand's for `history`, or undefined for an unknown topic. */
43276
+ const renderTopicHelp = (topic) => {
43277
+ const two = commandByToken(topic.slice(0, 2).join(" "));
43278
+ if (two !== void 0 && topic.length >= 2) return renderCommandHelp(two);
43279
+ const [first = ""] = topic;
43280
+ const one = commandByToken(first);
43281
+ if (one !== void 0) return renderCommandHelp(one);
43282
+ const extra = EXTRA_TOPICS.find(([term]) => term.split(" ")[0] === first);
43283
+ if (extra !== void 0) return `Usage: gtd ${extra[0]}\n\n${lines(wrap(extra[2]))}`;
43284
+ const family = COMMAND_ROWS.filter((r) => r.token.startsWith(`${first} `));
43285
+ if (first !== "" && family.length > 0) return family.map(renderCommandHelp).join("\n");
43286
+ };
43287
+ /** A usage error's tail: the nearest candidate when `word` looks like a typo of one, and where the full list is. */
43288
+ const didYouMean = (word, candidates, prefix = "", pointer = "`gtd help` for every command") => {
43289
+ const guess = closest(word, candidates);
43290
+ return guess === void 0 ? ` — run ${pointer}` : ` — did you mean \`${prefix}${guess}\`? Run ${pointer}`;
43291
+ };
43292
+ const unknownOption = (tok, jsonSeen) => {
43293
+ const candidates = tok.startsWith("--") ? FLAGS.map((f) => f.name) : [];
43294
+ return {
43295
+ kind: "error",
43296
+ message: `gtd: unknown option '${tok}'${didYouMean(tok.split("=")[0], candidates, "", "`gtd help <command>` for its options")}`,
43297
+ jsonSeen
43298
+ };
43331
43299
  };
43332
43300
  const tokenize = (tail) => {
43333
43301
  const positionals = [];
43334
43302
  const byFlag = /* @__PURE__ */ new Map();
43335
43303
  const present = /* @__PURE__ */ new Set();
43336
43304
  let jsonSeen = false;
43305
+ let afterBareJson;
43337
43306
  for (let i = 0; i < tail.length; i++) {
43338
43307
  const original = tail[i];
43339
43308
  const tok = SINGLE_DASH_ALIASES.get(original) ?? original;
43340
- if (!tok.startsWith("--")) {
43309
+ if (!tok.startsWith("-") || tok === "-") {
43310
+ if (tail[i - 1] === "--json") afterBareJson ??= original;
43341
43311
  positionals.push(original);
43342
43312
  continue;
43343
43313
  }
43344
- const row = flagByToken(tok);
43345
- if (row === void 0) return {
43346
- kind: "error",
43347
- message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
43348
- jsonSeen
43349
- };
43314
+ const row = tok.startsWith("--") ? flagByToken(tok) : void 0;
43315
+ if (row === void 0) return unknownOption(tok, jsonSeen);
43350
43316
  const eq = tok.indexOf("=");
43351
43317
  const alreadyPresent = present.has(row.name);
43352
43318
  present.add(row.name);
43353
43319
  if (row.name === "--json") jsonSeen = true;
43354
43320
  if (row.arity === 0) {
43355
- if (eq !== -1) return {
43356
- kind: "error",
43357
- message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
43358
- jsonSeen
43359
- };
43321
+ if (eq !== -1) return unknownOption(tok, jsonSeen);
43360
43322
  if (!byFlag.has(row.name)) byFlag.set(row.name, []);
43361
43323
  continue;
43362
43324
  }
@@ -43407,7 +43369,8 @@ const tokenize = (tail) => {
43407
43369
  positionals,
43408
43370
  byFlag,
43409
43371
  present,
43410
- jsonSeen
43372
+ jsonSeen,
43373
+ ...afterBareJson !== void 0 ? { afterBareJson } : {}
43411
43374
  };
43412
43375
  };
43413
43376
  const usagePlan = (message, json, stdout = "") => ({
@@ -43459,18 +43422,38 @@ const buildLandCommand = (bag) => ({
43459
43422
  ...bag["--cost"] !== void 0 ? { cost: bag["--cost"] } : {},
43460
43423
  ...bag["--model"] !== void 0 ? { model: bag["--model"] } : {}
43461
43424
  });
43462
- const parseArgv = (argv) => {
43463
- if (argv.includes("--help") || argv.includes("-h")) return {
43425
+ const HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
43426
+ /** `--help`/`-h` anywhere: the named command's own help, else the overview — never a usage error, so a typo'd command still gets help. */
43427
+ const helpPlan = (argv) => {
43428
+ const tokenized = tokenize(argv.slice(2).filter((tok) => !HELP_FLAGS.has(tok)));
43429
+ const topic = tokenized.kind === "ok" ? tokenized.positionals : [];
43430
+ return {
43431
+ kind: "output",
43432
+ stdout: renderTopicHelp(topic) ?? renderHelp()
43433
+ };
43434
+ };
43435
+ /** `gtd help [topic…]`: an unknown topic is a usage error naming the nearest command. */
43436
+ const helpCommandPlan = (topic, json) => {
43437
+ if (topic.length === 0) return {
43464
43438
  kind: "output",
43465
43439
  stdout: renderHelp()
43466
43440
  };
43441
+ const text = renderTopicHelp(topic);
43442
+ if (text !== void 0) return {
43443
+ kind: "output",
43444
+ stdout: text
43445
+ };
43446
+ return usagePlan(`gtd: help: no command '${topic.join(" ")}'${didYouMean(topic[0], COMMAND_WORDS, "gtd help ")}`, json);
43447
+ };
43448
+ const parseArgv = (argv) => {
43449
+ if (argv.some((tok) => HELP_FLAGS.has(tok))) return helpPlan(argv);
43467
43450
  if (argv.includes("--version") || argv.includes("-V")) return {
43468
43451
  kind: "output",
43469
43452
  stdout: `${GTD_VERSION}\n`
43470
43453
  };
43471
43454
  const tokenized = tokenize(argv.slice(2));
43472
43455
  if (tokenized.kind === "error") return usagePlan(tokenized.message, tokenized.jsonSeen);
43473
- const { positionals, byFlag, present, jsonSeen } = tokenized;
43456
+ const { positionals, byFlag, present, jsonSeen, afterBareJson } = tokenized;
43474
43457
  const first = positionals[0];
43475
43458
  const workflowRaw = byFlag.get("--workflow")?.[0];
43476
43459
  const workflowPresent = workflowRaw !== void 0;
@@ -43478,13 +43461,14 @@ const parseArgv = (argv) => {
43478
43461
  kind: "output",
43479
43462
  stdout: `${GTD_VERSION}\n`
43480
43463
  };
43481
- if (first === "help") return {
43482
- kind: "output",
43483
- stdout: renderHelp()
43484
- };
43464
+ if (first === "help") return helpCommandPlan(positionals.slice(1), jsonSeen);
43485
43465
  const isHistory = first === "history";
43486
43466
  const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
43487
- if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref>, metrics [ref...], judges [ref...] or fetch [remote]", jsonSeen);
43467
+ if (isHistory && row === void 0) {
43468
+ const subcommands = COMMAND_ROWS.filter((r) => r.token.startsWith("history ")).map((r) => r.token.split(" ")[1]);
43469
+ const typo = closest(positionals[1] ?? "", subcommands);
43470
+ return usagePlan(typo !== void 0 ? `gtd history: unknown subcommand '${positionals[1]}' — did you mean \`gtd history ${typo}\`?` : "gtd history: expected a subcommand — list, show <ref>, metrics [ref...], judges [ref...] or fetch [remote]", jsonSeen);
43471
+ }
43488
43472
  const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
43489
43473
  const selectsStart = workflowPresent && first === void 0;
43490
43474
  const judgeRun = row?.kind === "judge" && positionals[1] === "run";
@@ -43492,15 +43476,18 @@ const parseArgv = (argv) => {
43492
43476
  if (row === void 0 && !selectsStart) {
43493
43477
  const violation = scopeViolation(void 0, present);
43494
43478
  if (violation !== void 0) return usagePlan(violation, jsonSeen);
43495
- if (first === void 0) return usagePlan("gtd: missing command — gtd decides and prints, a driver executes; run `gtd install`, or copy one from https://github.com/pmelab/gtd/blob/main/docs/driver.md's \"A complete minimal driver\" section and run that, or see usage above (`gtd --help`)", jsonSeen, renderHelp());
43496
- return usagePlan(`unknown command '${first}'`, jsonSeen);
43479
+ if (first === void 0) return usagePlan("gtd: missing command — run `gtd help` for every command, or `gtd install` to learn how to drive gtd", jsonSeen, SHORT_USAGE);
43480
+ return usagePlan(`gtd: unknown command '${first}'${didYouMean(first, COMMAND_WORDS, "gtd ")}`, jsonSeen);
43497
43481
  }
43498
43482
  const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun || isHistory ? 2 : 1);
43499
43483
  if (kind === "start" && first === void 0) {
43500
43484
  if (restPositionals.length > 0) return usagePlan(`gtd: too many arguments — expected none, got: ${restPositionals.join(", ")}`, jsonSeen);
43501
43485
  } else {
43502
- const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
43503
- if (arityMsg !== void 0) return usagePlan(arityMsg, jsonSeen);
43486
+ const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") || kind === "door" && restPositionals.length === 0 ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
43487
+ if (arityMsg !== void 0) {
43488
+ const hint = afterBareJson !== void 0 && restPositionals.includes(afterBareJson) ? ` — to print one field, write --json=${afterBareJson}` : "";
43489
+ return usagePlan(arityMsg + hint, jsonSeen);
43490
+ }
43504
43491
  }
43505
43492
  const violation = scopeViolation(kind, present);
43506
43493
  if (violation !== void 0) return usagePlan(violation, jsonSeen);
@@ -43542,7 +43529,7 @@ const parseArgv = (argv) => {
43542
43529
  kind: "command",
43543
43530
  command: {
43544
43531
  kind: "door",
43545
- name: restPositionals[0],
43532
+ ...restPositionals[0] !== void 0 ? { name: restPositionals[0] } : {},
43546
43533
  args: restPositionals.slice(1)
43547
43534
  },
43548
43535
  json,