@pmelab/gtd 59.0.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.
@@ -22068,7 +22068,7 @@ const defineWorkflow = (schema, build) => {
22068
22068
  options
22069
22069
  },
22070
22070
  then: () => {
22071
- 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");
22072
22072
  }
22073
22073
  });
22074
22074
  return Object.assign(factory, { [FACTORY]: internals });
@@ -27851,7 +27851,7 @@ function tokenizeTitleBefore(effects, ok, nok) {
27851
27851
  return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
27852
27852
  }
27853
27853
  }
27854
- var definition, titleBefore;
27854
+ var definition$1, titleBefore;
27855
27855
  var init_definition = __esmMin((() => {
27856
27856
  init_micromark_factory_destination();
27857
27857
  init_micromark_factory_label();
@@ -27860,7 +27860,7 @@ var init_definition = __esmMin((() => {
27860
27860
  init_micromark_factory_whitespace();
27861
27861
  init_micromark_util_character();
27862
27862
  init_micromark_util_normalize_identifier();
27863
- definition = {
27863
+ definition$1 = {
27864
27864
  name: "definition",
27865
27865
  tokenize: tokenizeDefinition
27866
27866
  };
@@ -30848,7 +30848,7 @@ var init_constructs = __esmMin((() => {
30848
30848
  [57]: list,
30849
30849
  [62]: blockQuote
30850
30850
  };
30851
- contentInitial = { [91]: definition };
30851
+ contentInitial = { [91]: definition$1 };
30852
30852
  flowInitial = {
30853
30853
  [-2]: codeIndented,
30854
30854
  [-1]: codeIndented,
@@ -37996,7 +37996,7 @@ const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
37996
37996
  * `gtd judge answer` (the only other caller of `planLanding`/`landFields`)
37997
37997
  * passes its own two-word name so the hint stays runnable verbatim.
37998
37998
  */
37999
- 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);
38000
38000
  //#endregion
38001
38001
  //#region src/wire/Judge.ts
38002
38002
  const judgeJson = (state, questions) => JSON.stringify({
@@ -39140,7 +39140,7 @@ const dedupeDiagnostics = (diagnostics) => {
39140
39140
  //#endregion
39141
39141
  //#region src/workflow/load.ts
39142
39142
  init_esm();
39143
- 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")}`);
39144
39144
  const failOnErrors = (diagnostics) => diagnostics.some((d) => d.severity === "error") ? fail$7(configError(diagnostics)) : _void;
39145
39145
  const MIGRATION_DOC = "https://github.com/pmelab/gtd/blob/main/docs/configuration.md#migrating-from-gtdrc";
39146
39146
  /**
@@ -39156,7 +39156,7 @@ const refuseLegacyRc = gen(function* () {
39156
39156
  const found = files.filter((f) => f.owned).map((f) => f.path);
39157
39157
  if (found.length === 0) return;
39158
39158
  return yield* fail$7(new GtdError([
39159
- "gtd config:",
39159
+ "gtd: config:",
39160
39160
  ...found.map((file) => ` - ${file}: no longer read — delete it`),
39161
39161
  " gtd 23 removed the .gtdrc files; move each key they set:",
39162
39162
  " vars → an option of the workflow factory in gtd.config.ts, or its `defaults` export",
@@ -39192,7 +39192,7 @@ const load = gen(function* () {
39192
39192
  });
39193
39193
  const loaded = yield* try_({
39194
39194
  try: () => loadWorkflows(module, shipped),
39195
- 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)}`)
39196
39196
  });
39197
39197
  const compiled = loaded.config;
39198
39198
  const diagnostics = dedupeDiagnostics(sortDiagnostics([...compiled.diagnostics, ...loaded.diagnostics]));
@@ -39697,7 +39697,7 @@ const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => r
39697
39697
  })), (outcome) => {
39698
39698
  if (outcome.kind === "rest") return succeed$3(outcome.rest.name);
39699
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";
39700
- 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}`));
39701
39701
  });
39702
39702
  /**
39703
39703
  * The default entry's first step — where a finished process waits — found the
@@ -39713,13 +39713,13 @@ const firstStep = (loaded, head) => gen(function* () {
39713
39713
  const env = watchedEmptySettings();
39714
39714
  const outcome = yield* either(replayToFirstStep(loaded, vars.settings, env.settings, empty.tree));
39715
39715
  const reads = [...settingReads("process", vars), ...settingReads("environment", env)];
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`));
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`));
39717
39717
  if (outcome._tag === "Left") return yield* fail$7(outcome.left);
39718
39718
  const name = outcome.right;
39719
39719
  if (!empty.read()) return name;
39720
39720
  const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
39721
39721
  if (atHead === name) return name;
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`));
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`));
39723
39723
  });
39724
39724
  var ConfigService = class ConfigService extends Tag("ConfigService")() {
39725
39725
  static Live = succeed$2(ConfigService, { load });
@@ -39727,7 +39727,7 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
39727
39727
  //#endregion
39728
39728
  //#region src/workflow/skills.ts
39729
39729
  init_esm();
39730
- 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}`);
39731
39731
  const checkShape = (value) => {
39732
39732
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("skills export must return an object of scope -> skill names");
39733
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`);
@@ -40514,7 +40514,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
40514
40514
  const step = outcome.rest;
40515
40515
  yield* (yield* Narrator).narrate(`rest resolved: ${step.name} (awaits ${step.actor})`);
40516
40516
  const stepDef = stepDefOf(step);
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(", ")})`));
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(", ")})`));
40518
40518
  const changes = ref === void 0 ? (yield* pendingChanges(git, stepDef.approve === true)).map((e) => ({
40519
40519
  status: normalizeStatus(e.status),
40520
40520
  path: e.path
@@ -41054,7 +41054,7 @@ const emitScripts = (required = [], optional = []) => ({
41054
41054
  optional: assembleScript(optional)
41055
41055
  });
41056
41056
  /** Exported so `src/twins/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
41057
- 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";
41058
41058
  const PRESENTATION_ONLY_COMMENT = "# presentation only — safe to skip";
41059
41059
  const PRESENTATION_FAILURE_WARNING = "printf 'gtd: presentation-only follow-up failed — continuing\\n' >&2";
41060
41060
  /**
@@ -42235,7 +42235,10 @@ const runLandCommand = (opts, json, out) => gen(function* () {
42235
42235
  const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => gen(function* () {
42236
42236
  const config = yield* (yield* ConfigService).load;
42237
42237
  const resolved = config.workflowNamed(workflow);
42238
- 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
+ }
42239
42242
  const rest = yield* restAt(void 0, varOverrides);
42240
42243
  const started = yield* startRefusal(rest, resolved, varOverrides);
42241
42244
  const plan = yield* planStart({
@@ -42249,9 +42252,24 @@ const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => ge
42249
42252
  config: rest.config
42250
42253
  });
42251
42254
  if (plan.kind === "refusal") return yield* fail$7(new Error(plan.message));
42252
- out.write(landingScript(ScriptSurface.render(plan.steps)));
42255
+ out.write(listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
42253
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
+ };
42254
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
+ };
42255
42273
  /**
42256
42274
  * `gtd door <name> [args…]`: `gtd --workflow` with the door's workflow and the
42257
42275
  * process settings its `vars` derive from the positional args. A wrong name or
@@ -42259,11 +42277,8 @@ const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => ar
42259
42277
  */
42260
42278
  const runDoorCommand = (name, args, out) => gen(function* () {
42261
42279
  const config = yield* (yield* ConfigService).load;
42262
- const door = config.doors.get(name);
42263
- if (door === void 0) {
42264
- const known = [...config.doors];
42265
- return yield* fail$7(new GtdUsageError(`gtd door: unknown door "${name}" — doors: ${known.map(([n, d]) => doorSynopsis(n, d)).join(", ")}`));
42266
- }
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));
42267
42282
  const declared = door.args ?? [];
42268
42283
  const required = declared.filter((arg) => arg.optional !== true).length;
42269
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)}`));
@@ -42281,6 +42296,7 @@ const runDoorCommand = (name, args, out) => gen(function* () {
42281
42296
  const runDoorsCommand = (json, out) => gen(function* () {
42282
42297
  const doors = [...(yield* (yield* ConfigService).load).doors];
42283
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");
42284
42300
  out.write(doors.map(([n, d]) => `${doorSynopsis(n, d)} → ${d.workflow}\n`).join(""));
42285
42301
  return;
42286
42302
  }
@@ -42428,6 +42444,13 @@ const runNextCommand = (json, out) => gen(function* () {
42428
42444
  else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
42429
42445
  else {
42430
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
+ }
42431
42454
  out.write(renderBeatPlain(fields, validateCommand));
42432
42455
  }
42433
42456
  });
@@ -42583,12 +42606,13 @@ const runUiCliCommand = (command, out) => gen(function* () {
42583
42606
  * cwds (e.g. macOS /tmp → /private/tmp) match.
42584
42607
  */
42585
42608
  const assertRunningFromRepoRoot = (git, host) => gen(function* () {
42586
- const topLevel = yield* git.topLevel();
42587
- 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})`));
42588
42611
  });
42612
+ const shellWord = (path) => /^[\w@%+=:,./-]+$/.test(path) ? path : `'${path.replace(/'/g, "'\\''")}'`;
42589
42613
  /** A command that derives workflow state needs at least one commit too — a commitless repository has no HEAD to resolve a `Rest` at. */
42590
42614
  const assertRepositoryHasCommits = (git) => gen(function* () {
42591
- 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"));
42592
42616
  });
42593
42617
  /**
42594
42618
  * What a command kind needs before it may run. Defined here, not in
@@ -42675,6 +42699,57 @@ const runCommand = (command, json, out) => {
42675
42699
  });
42676
42700
  };
42677
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
42678
42753
  //#region src/cli/Cli.ts
42679
42754
  init_esm();
42680
42755
  const nonNegativeNumber = (raw, flag) => {
@@ -42703,20 +42778,8 @@ const FLAGS = [
42703
42778
  decode: ([raw]) => right(raw ?? ""),
42704
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",
42705
42780
  valueHint: "<path>",
42706
- help: [
42707
- "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
42708
- "list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
42709
- "--json=<path> (a dotted key path into that document, e.g.",
42710
- "kind, content, session.id) prints just that value: a",
42711
- "scalar raw and unquoted, a boolean as true/false, a list",
42712
- "one JSON entry per line — a numeric segment (e.g.",
42713
- "skills.0) indexes into one entry of a list field instead.",
42714
- "An absent optional field prints nothing and exits 0 —",
42715
- "including when an earlier segment of <path> is itself",
42716
- "absent/null (e.g. session.id at a non-prompt rest), which",
42717
- "never counts as unknown; an unknown path is a usage error",
42718
- "(exit 2)."
42719
- ]
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" }
42720
42783
  },
42721
42784
  {
42722
42785
  name: "--port",
@@ -42729,11 +42792,7 @@ const FLAGS = [
42729
42792
  },
42730
42793
  scopeError: "gtd: --port is only valid for `gtd ui`",
42731
42794
  valueHint: "<n>",
42732
- help: [
42733
- "(gtd ui only) the tailscale serve port (default: the first",
42734
- "free of 8443, 10000, 443), or the bind port when --host",
42735
- "opts out of serve (default: a free port)"
42736
- ]
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)"
42737
42796
  },
42738
42797
  {
42739
42798
  name: "--host",
@@ -42743,11 +42802,7 @@ const FLAGS = [
42743
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),
42744
42803
  scopeError: "gtd: --host is only valid for `gtd ui`",
42745
42804
  valueHint: "<addr>",
42746
- help: [
42747
- "(gtd ui only) opt out of the default tailscale serve front",
42748
- "door and bind this address directly instead, showing it in",
42749
- "the printed URL"
42750
- ]
42805
+ help: "Bind this address directly instead of publishing through tailscale serve"
42751
42806
  },
42752
42807
  {
42753
42808
  name: "--self-signed",
@@ -42757,7 +42812,7 @@ const FLAGS = [
42757
42812
  decode: () => right(true),
42758
42813
  scopeError: "gtd: --self-signed is only valid for `gtd ui`",
42759
42814
  valueHint: "",
42760
- 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"
42761
42816
  },
42762
42817
  {
42763
42818
  name: "--dev",
@@ -42767,7 +42822,7 @@ const FLAGS = [
42767
42822
  decode: () => right(true),
42768
42823
  scopeError: "gtd: --dev is only valid for `gtd ui`",
42769
42824
  valueHint: "",
42770
- 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"
42771
42826
  },
42772
42827
  {
42773
42828
  name: "--provider",
@@ -42777,11 +42832,7 @@ const FLAGS = [
42777
42832
  decode: ([raw]) => raw === "fixed" || raw === "jev" || raw === "llm" ? right(raw) : left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
42778
42833
  scopeError: "gtd: --provider is only valid for `gtd judge run`",
42779
42834
  valueHint: "<name>",
42780
- help: [
42781
- "(gtd judge run only) the answerer: fixed | jev | llm;",
42782
- "omitted: GTD_JUDGE_PROVIDER, then judge.json, then jev",
42783
- "when TYPESAFE_API_KEY is set, else llm"
42784
- ]
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)"
42785
42836
  },
42786
42837
  {
42787
42838
  name: "--answers",
@@ -42791,11 +42842,7 @@ const FLAGS = [
42791
42842
  decode: ([raw]) => raw === void 0 || raw === "" ? left("gtd: --answers must be a non-empty path") : right(raw),
42792
42843
  scopeError: "gtd: --answers is only valid for `gtd judge run`",
42793
42844
  valueHint: "<path>",
42794
- help: [
42795
- "(gtd judge run --provider fixed only) a JSON verdict array",
42796
- "[{ id, answer, p }]; else the GTD_JUDGE_ANSWERS env var",
42797
- "(inline JSON) is read"
42798
- ]
42845
+ help: "With --provider fixed: a JSON verdict file [{ id, answer, p }] (default: $GTD_JUDGE_ANSWERS, inline JSON)"
42799
42846
  },
42800
42847
  {
42801
42848
  name: "--cost",
@@ -42805,7 +42852,7 @@ const FLAGS = [
42805
42852
  decode: ([raw]) => nonNegativeNumber(raw ?? "", "--cost"),
42806
42853
  scopeError: "gtd: --cost is only valid for `gtd land` — a start is not a metered agent turn",
42807
42854
  valueHint: "<n>",
42808
- 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"
42809
42856
  },
42810
42857
  {
42811
42858
  name: "--model",
@@ -42815,11 +42862,11 @@ const FLAGS = [
42815
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),
42816
42863
  scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
42817
42864
  valueHint: "<name>",
42818
- help: [
42819
- "(gtd land, with --cost) tag that cost's model",
42820
- "(gtd judge run, llm answerer) the claude model; omitted:",
42821
- "GTD_JUDGE_MODEL, then judge.json, then sonnet"
42822
- ]
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
+ }
42823
42870
  },
42824
42871
  {
42825
42872
  name: "--workflow",
@@ -42829,7 +42876,7 @@ const FLAGS = [
42829
42876
  decode: ([raw]) => right(raw ?? ""),
42830
42877
  scopeError: "gtd: --workflow is only valid with no other command — use the bare `gtd --workflow <name>` form; landing and starting are different verbs",
42831
42878
  valueHint: "<name>",
42832
- 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"
42833
42880
  },
42834
42881
  {
42835
42882
  name: "--var",
@@ -42860,14 +42907,11 @@ const FLAGS = [
42860
42907
  },
42861
42908
  scopeError: "gtd: --var requires --workflow (or `gtd history judges`)",
42862
42909
  valueHint: "<name>=<value>",
42863
- help: [
42864
- "(with --workflow; repeatable) pin a process setting for the",
42865
- "new process; the name must already be declared by the",
42866
- "workflow's defaults (environment settings are not",
42867
- "pinnable). (gtd history judges) re-decide past verdicts",
42868
- "under this threshold: judgeIdenticalMinP or",
42869
- "qualityTriageMinP"
42870
- ]
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
+ }
42871
42915
  },
42872
42916
  {
42873
42917
  name: "--jsonl",
@@ -42877,7 +42921,7 @@ const FLAGS = [
42877
42921
  decode: () => right(true),
42878
42922
  scopeError: "gtd: --jsonl is only valid for `gtd history judges`",
42879
42923
  valueHint: "",
42880
- 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",
42881
42925
  conflicts: ["--json", "--var"]
42882
42926
  },
42883
42927
  {
@@ -42888,12 +42932,7 @@ const FLAGS = [
42888
42932
  decode: () => right(true),
42889
42933
  scopeError: "gtd: --open-questions is only valid for `gtd check`",
42890
42934
  valueHint: "",
42891
- help: [
42892
- "(gtd check only) ignore <mode>'s structural findings and",
42893
- "instead run the qa open-questions predicate over <file>,",
42894
- "printing each unanswered question one per line and exiting",
42895
- "non-zero when any remain"
42896
- ]
42935
+ help: "Instead of <mode>'s findings, list the unanswered questions in <file>; exits 1 when any remain"
42897
42936
  },
42898
42937
  {
42899
42938
  name: "--open-threads",
@@ -42903,15 +42942,7 @@ const FLAGS = [
42903
42942
  decode: () => right(true),
42904
42943
  scopeError: "gtd: --open-threads is only valid for `gtd check`",
42905
42944
  valueHint: "",
42906
- help: [
42907
- "(gtd check only) ignore <mode>'s structural findings and",
42908
- "instead list every open thread (a footnote whose last",
42909
- "entry is the agent's) over <file>, one per line with its",
42910
- "first H entry, exiting non-zero when any are open. With no",
42911
- "<mode>/<file>, lists code-comment threads in the files the",
42912
- "current process changed (read from its diff base, unlike",
42913
- "the rest of `gtd check`)"
42914
- ]
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"
42915
42946
  },
42916
42947
  {
42917
42948
  name: "--verbose",
@@ -42921,11 +42952,7 @@ const FLAGS = [
42921
42952
  decode: () => right(true),
42922
42953
  scopeError: "gtd: --verbose is valid for every command — this error is unreachable",
42923
42954
  valueHint: "",
42924
- help: [
42925
- "enable stderr narration for this invocation: which rest",
42926
- "resolved and how config resolved across layers. Aliased",
42927
- "to -v"
42928
- ]
42955
+ help: "Narrate on stderr what gtd resolved and where its config came from"
42929
42956
  }
42930
42957
  ];
42931
42958
  /**
@@ -42944,203 +42971,39 @@ const flagByToken = (token) => {
42944
42971
  return FLAGS.find((f) => f.name === bareName);
42945
42972
  };
42946
42973
  const COMMAND_ROWS = [
42947
- {
42948
- token: "land",
42949
- kind: "land",
42950
- arity: "none",
42951
- details: [
42952
- "Land whatever the tree now shows at the currently resolved",
42953
- "rest — a human capture, an agent/check turn, or an empty",
42954
- "attempt (a fruitless prompt turn). Pass",
42955
- "--cost=<n> (optionally --model=<name>) to record the",
42956
- "just-finished invocation's token cost and model on the",
42957
- "turn commit (summed into it.processCost/",
42958
- "processCostByModel). Plain (the default) prints one",
42959
- "human-readable sentence naming the commit subject (or a",
42960
- "no-op note) plus a pointer to `gtd land --json=script | sh`",
42961
- "to get the landing script — never the script itself.",
42962
- "--json emits script (the actual POSIX sh",
42963
- "a driver runs) alongside settled, idle, state (the",
42964
- "post-land target), subject, cost and model. Exits 0 on",
42965
- "success, 1 on any refusal — see the Exit codes section",
42966
- "below"
42967
- ]
42968
- },
42969
- {
42970
- token: "abandon",
42971
- kind: "abandon",
42972
- arity: "none",
42973
- details: [
42974
- "End the process currently underway without completing it:",
42975
- "rewind HEAD to the commit the process started from,",
42976
- "keeping everything it produced as uncommitted changes. A",
42977
- "no-op when no process is underway"
42978
- ]
42979
- },
42980
- {
42981
- token: "restore",
42982
- kind: "restore",
42983
- arity: "none",
42984
- details: [
42985
- "Hard-reset HEAD back to the tip retained by the last",
42986
- "abandon (refs/worktree/gtd/history), bringing back an",
42987
- "abandoned process's turns. Refuses on a dirty working",
42988
- "tree, when there is no retained history, or when HEAD has",
42989
- "advanced past the retained tip with commits that would be",
42990
- "lost"
42991
- ]
42992
- },
42993
42974
  {
42994
42975
  token: "next",
42995
42976
  kind: "next",
42996
42977
  arity: "none",
42997
- details: [
42998
- "Print the resolved rest's beat (no mutation, safe to",
42999
- "poll), in one of three encodings. Plain (the default): a",
43000
- "status summary, a blank line, then the step verbatim — at a",
43001
- "script/capture rest an instruction line ('Run this",
43002
- "script:' / 'The edit is already made — run `gtd land` to",
43003
- "land it.') precedes the status summary; at a prompt rest",
43004
- "it's the bare step (plus the self-validation instruction",
43005
- "when applicable) with no header at all, since those bytes",
43006
- "are the agent's own input. --json emits the one structured",
43007
- "surface gtd has: kind (capture|message|script|prompt|",
43008
- "stalled) selects what a driver does, content is what it",
43009
- "runs or shows, idle marks the workflow's initial state with",
43010
- "a clean tree, plus the prompt session, model, validate",
43011
- "script, log path, changes, next and the resting state's own",
43012
- "fields. --json=<path> (a dotted key path into that same",
43013
- "document, e.g. kind, content, session.id) prints just that",
43014
- "value instead of the whole document — see --json's own help",
43015
- "above. Exits 0 — see the Exit codes section below"
43016
- ]
43017
- },
43018
- {
43019
- token: "validate",
43020
- kind: "validate",
43021
- arity: "none",
43022
- details: [
43023
- "Print the script that formats (when declared) then",
43024
- "validates the resolved rest's steering file, using its",
43025
- "mode's commands (its file:/mode:), instead of running it —",
43026
- "a driver runs the script and reads the findings from its",
43027
- "own exit code/output. Always exits 0; prints \"nothing to",
43028
- "validate\" when the resolved rest declares nothing to run.",
43029
- "On a non-zero validate exit the emitted script prints a",
43030
- "ready-to-send fix prompt (instruction + findings) and",
43031
- "exits with the validator's own code"
43032
- ]
43033
- },
43034
- {
43035
- token: "lsp",
43036
- kind: "lsp",
43037
- arity: "none",
43038
- details: ["Start the LSP server for .gtd/ steering files (stdio)"]
43039
- },
43040
- {
43041
- token: "ui",
43042
- kind: "ui",
43043
- arity: "none",
43044
- details: [
43045
- "Expose gtd's web/phone client for THIS worktree — the",
43046
- "invoking directory, never a configured list of roots — and",
43047
- "refuses outside a repository like every other state command.",
43048
- "By default, publishes through `tailscale serve` (reachable",
43049
- "from anywhere on the tailnet, including over a DERP relay)",
43050
- "with tailscaled terminating TLS, walking 8443, then 10000,",
43051
- "then 443 until one publishes; falls back to binding a local",
43052
- "HTTPS server directly, never refusing, when none of the",
43053
- "three works. --host <addr> opts out of serve and binds that",
43054
- "address directly instead; --port <n> overrides the serve",
43055
- "port (default: the first free of 8443, 10000, 443), or the",
43056
- "bind port when --host is given (default: a free port);",
43057
- "--self-signed also opts out of serve, generating a",
43058
- "throwaway TLS certificate instead of the configured",
43059
- "ui.cert/ui.key; --dev runs against local development sources",
43060
- "instead of the packaged build"
43061
- ]
43062
- },
43063
- {
43064
- token: "check",
43065
- kind: "check",
43066
- arity: { names: ["mode", "file"] },
43067
- details: [
43068
- "Read <file> and run the built-in steering format named",
43069
- "<mode> (see `gtd validate`'s modes: qa, review, findings)",
43070
- "over its contents, printing each finding one per line and",
43071
- "exiting non-zero when there are any. Resolves no workflow",
43072
- "state and reads no config — standalone, runnable from any",
43073
- "directory with <mode>/<file> given explicitly. This is what a",
43074
- "workflow's emitted validation script invokes as a leaf step.",
43075
- "--open-questions runs the qa unanswered-questions predicate",
43076
- "instead (see --help); --open-threads lists open threads.",
43077
- "`gtd check --open-threads` alone lists open code-comment",
43078
- "threads (`// H: ...`) in the files the process changed;",
43079
- "it prints nothing when no process is underway."
43080
- ]
43081
- },
43082
- {
43083
- token: "uncheck",
43084
- kind: "uncheck",
43085
- arity: { name: "file" },
43086
- details: [
43087
- "Read <file> and reset every review-mode `- [x]`/`- [X]`",
43088
- "file-pointer box back to `- [ ]`, writing the result back",
43089
- "only when the bytes actually changed. Resolves no workflow",
43090
- "state and reads no config — standalone, runnable from any",
43091
- "directory with <file> given explicitly. This is what the",
43092
- "landing script runs ahead of the commit at the human review",
43093
- "gate, so no tick ever reaches a commit. Takes no <mode> —",
43094
- "it means review-mode file pointers and nothing else, never",
43095
- "qa-mode's answered-question boxes. A missing file writes",
43096
- "nothing and exits 0"
43097
- ]
43098
- },
43099
- {
43100
- token: "install",
43101
- kind: "install",
43102
- arity: "none",
43103
- details: [
43104
- "Print a complete, self-contained briefing that teaches an",
43105
- "agent (or a human) to build a gtd driver in any shell or",
43106
- "runtime — the self-serve version of",
43107
- "https://github.com/pmelab/gtd/blob/main/docs/driver.md's",
43108
- "'Writing your own driver', and how to get a workflow: gtd",
43109
- "ships none. It writes nothing."
43110
- ]
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).`
43111
42985
  },
43112
42986
  {
43113
- token: "summary",
43114
- kind: "summary",
42987
+ token: "land",
42988
+ kind: "land",
43115
42989
  arity: "none",
43116
- details: [
43117
- "Print the prompt for an agent to write the process HEAD",
43118
- "closes or sits inside its own closing message — the entry",
43119
- "commit, each human-authored commit (a review round, an",
43120
- "answered question gate), the diff range to inspect, and",
43121
- "it.processCost/processCostByModel. Writes nothing: no git,",
43122
- "no state transition, no file, no session identity — the",
43123
- "driver pipes the output to a cold agent and does what it",
43124
- "wants with the result (a squash, an amend, a PR body).",
43125
- "Refuses (exit 1) when the workflow declares no summary:",
43126
- "template, or when the resolved run has no commits to name —",
43127
- "it is runnable any time before the next thing lands on the",
43128
- "branch"
43129
- ]
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.`
43130
42999
  },
43131
43000
  {
43132
- token: "base",
43133
- kind: "base",
43001
+ token: "validate",
43002
+ kind: "validate",
43134
43003
  arity: "none",
43135
- details: [
43136
- "Print the review anchor hash — the diff base an external",
43137
- "tool (a diff, a PR tool, another agent) should point at —",
43138
- "bare, newline-terminated, and nothing else. Writes nothing:",
43139
- "no git, no state transition, no session identity. Before",
43140
- "the first review round it's the process's diff base;",
43141
- "afterward it's the most-recent review round's boundary.",
43142
- "Refuses (exit 1) when no process is underway."
43143
- ]
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.`
43144
43007
  },
43145
43008
  {
43146
43009
  token: "door",
@@ -43149,73 +43012,55 @@ const COMMAND_ROWS = [
43149
43012
  name: "name",
43150
43013
  variadic: "args"
43151
43014
  },
43152
- details: [
43153
- "Start a process through a named shortcut, mapping the",
43154
- "positional args to that workflow's process settings — the",
43155
- "same start `gtd --workflow` makes, printed the same way.",
43156
- "Each workflow the config exports brings its own doors (gtd's",
43157
- "`fix`, `review [base]`, `rollout <flag>` and",
43158
- "`cleanup <flag>`), plus the config's `doors` export. An",
43159
- "unknown door, or too few or too many args, is a usage error",
43160
- "(exit 2) naming the doors or the door's synopsis"
43161
- ]
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.`
43162
43020
  },
43163
43021
  {
43164
43022
  token: "doors",
43165
43023
  kind: "doors",
43166
43024
  arity: "none",
43167
- details: [
43168
- "List every door, sorted: one `name [args] → workflow` line",
43169
- "each. --json prints one JSON array of { name, workflow,",
43170
- "args: [{ name, optional }] } instead; --json=<path> reads one",
43171
- "value off it. Writes nothing"
43172
- ]
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.`
43173
43028
  },
43174
43029
  {
43175
- token: "judge",
43176
- kind: "judge",
43030
+ token: "abandon",
43031
+ kind: "abandon",
43177
43032
  arity: "none",
43178
- details: [
43179
- "Print the resolved rest's pending judgment — the prepared",
43180
- "state, its typed questions, and their criteria — the same",
43181
- "judge field `gtd next --json` already carries. Read-only:",
43182
- "resolves no session, writes nothing. Refuses (exit 1) when",
43183
- "the resolved rest declares no judge:"
43184
- ]
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"`
43185
43038
  },
43186
43039
  {
43187
- token: "judge answer",
43188
- kind: "judgeAnswer",
43040
+ token: "restore",
43041
+ kind: "restore",
43189
43042
  arity: "none",
43190
- details: [
43191
- "Read a verdict off stdin — one { id, answer, p } entry per",
43192
- "question the pending judgment declared — and decode it",
43193
- "against an Effect Schema built from those same question",
43194
- "ids. Refuses (exit 1) when the resolved rest declares no",
43195
- "judge:. Exits 2 (a usage error, like an unknown --json",
43196
- "selector) when stdin isn't valid JSON or the verdict",
43197
- "doesn't decode against the pending question ids."
43198
- ]
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.`
43199
43046
  },
43200
43047
  {
43201
- token: "judge run",
43202
- kind: "judgeRun",
43048
+ token: "summary",
43049
+ kind: "summary",
43203
43050
  arity: "none",
43204
- details: [
43205
- "Answer a judgment: read the `gtd judge --json` document off",
43206
- "stdin, write a verdict [{ id, answer, p }] on stdout — what",
43207
- "`gtd judge answer` decodes. --provider fixed answers from",
43208
- "--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
43209
- "does not cover are left out. --provider jev asks TypeSafe",
43210
- "(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
43211
- "on PATH (Claude Code only), model sonnet unless --model.",
43212
- "No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
43213
- "judge.json, else jev when TYPESAFE_API_KEY is set and no",
43214
- "model is given, else llm; --model likewise falls back to",
43215
- "GTD_JUDGE_MODEL, then the file's model. Needs no",
43216
- "repository. jev and llm exit 1 when they cannot answer",
43217
- "every question, with nothing on stdout"
43218
- ]
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.`
43219
43064
  },
43220
43065
  {
43221
43066
  token: "strays",
@@ -43224,45 +43069,27 @@ const COMMAND_ROWS = [
43224
43069
  name: "range",
43225
43070
  variadic: "glob"
43226
43071
  },
43227
- details: [
43228
- "List the files <range> (<base>..<tip>, an empty side is HEAD)",
43229
- "touched in its committed net diff that match none of the",
43230
- "globs. Anchored at the repo root: * stays within one path",
43231
- "segment, ** crosses them, everything else is literal — quote",
43232
- "globs in the shell. stdout: a sorted JSON array, [] when",
43233
- "clean; stderr: a summary, plus one stray: line per path with",
43234
- "--verbose. --json=<n> picks one entry, nothing past the end;",
43235
- "a symlink is a path like any other, never followed; a",
43236
- "submodule counts as its path, whatever diff.ignoreSubmodules",
43237
- "or .gitmodules ignore say. Runs in any git repository, no",
43238
- "process needed. Exits 10 when",
43239
- "strays were found, 1 on an unknown revision, 2 on a bad range"
43240
- ]
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.`
43241
43077
  },
43242
43078
  {
43243
43079
  token: "history list",
43244
43080
  kind: "historyList",
43245
43081
  arity: "none",
43246
- details: [
43247
- "List the finished processes kept behind refs/gtd/history/*,",
43248
- "newest first: entry, branch, tip, date and subject per line.",
43249
- "--json prints one array of { ref, entry, branch, tip, date,",
43250
- "subject }. Writes nothing and never replays; with no refs it",
43251
- "hints at `gtd history fetch | sh` and still exits 0"
43252
- ]
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\`.`
43253
43085
  },
43254
43086
  {
43255
43087
  token: "history show",
43256
43088
  kind: "historyShow",
43257
43089
  arity: { name: "ref" },
43258
- details: [
43259
- "Replay one kept process under today's config and print its",
43260
- "steps, judge verdicts and approvals. <ref> is a full ref, an",
43261
- "entry hash (or unique prefix) or a branch name. Refuses",
43262
- "(exit 1) when a config file differs from its content at the",
43263
- "ref's tip. A version mismatch only warns on stderr. --json",
43264
- "prints the trail as one document; writes nothing"
43265
- ]
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.`
43266
43093
  },
43267
43094
  {
43268
43095
  token: "history metrics",
@@ -43271,18 +43098,9 @@ const COMMAND_ROWS = [
43271
43098
  name: "ref",
43272
43099
  many: true
43273
43100
  },
43274
- details: [
43275
- "Report how the kept processes went: builds whose naive pass",
43276
- "landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
43277
- "model and phase with the double work of a rebuild, quality-lap",
43278
- "findings per lens, and red checks that passed on their",
43279
- "re-check — per process and in total. Each <ref>",
43280
- "is read as by history show, but a branch name means every",
43281
- "process kept from it; none means every ref.",
43282
- "Reads commit subjects and trailers, never replays, so a",
43283
- "changed config is no refusal. --json prints { processes, total",
43284
- "} as one document; writes nothing"
43285
- ]
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.`
43286
43104
  },
43287
43105
  {
43288
43106
  token: "history judges",
@@ -43291,18 +43109,9 @@ const COMMAND_ROWS = [
43291
43109
  name: "ref",
43292
43110
  many: true
43293
43111
  },
43294
- details: [
43295
- "Calibrate the judges of the kept processes from their",
43296
- "recorded Gtd-Judge verdicts: per judge (retry, triage, clear)",
43297
- "and p bucket, how many verdicts later events overturned.",
43298
- "--var <name>=<value> re-decides every past verdict under",
43299
- "another threshold and prints what would have changed;",
43300
- "--jsonl prints one { process, judge, commit, step, question,",
43301
- "state, answer, p, outcome } line per verdict instead. <ref>",
43302
- "is read as by history metrics. Never replays and asks no",
43303
- "model. --json prints { processes, total, whatIf }; writes",
43304
- "nothing"
43305
- ]
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.`
43306
43115
  },
43307
43116
  {
43308
43117
  token: "history fetch",
@@ -43311,69 +43120,205 @@ const COMMAND_ROWS = [
43311
43120
  name: "remote",
43312
43121
  optional: true
43313
43122
  },
43314
- details: [
43315
- "Print a script that fetches every refs/gtd/history/* from",
43316
- "<remote> (default origin) and reports how many arrived — run",
43317
- "it with `gtd history fetch | sh`. Never runs git itself"
43318
- ]
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.`
43319
43194
  }
43320
43195
  ];
43321
43196
  const commandByToken = (token) => COMMAND_ROWS.find((r) => r.token === token);
43322
- const COLUMN = 19;
43323
- const renderBlock = (header, lines) => {
43324
- const [first, ...rest] = lines;
43325
- const headerCell = ` ${header}`;
43326
- return (headerCell.length < COLUMN ? `${headerCell.padEnd(COLUMN)}${first ?? ""}\n` : `${headerCell}\n${" ".repeat(COLUMN)}${first ?? ""}\n`) + rest.map((l) => `${" ".repeat(COLUMN)}${l}\n`).join("");
43327
- };
43328
- const START_SHORT_FORM = renderBlock("(no command) --workflow <name>", ["Starts a new process authenticated as human, e.g.", "'gtd --workflow <name>'"]);
43329
- const VERSION_BLOCK = renderBlock("version", ["Print version and exit"]);
43330
- 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
+ };
43331
43214
  const SPACE_FORM_FLAGS = /* @__PURE__ */ new Set(["--workflow", "--var"]);
43332
43215
  const flagHeader = (row) => {
43333
43216
  if (row.valueHint === "") return row.name;
43217
+ if (row.arity === "optional") return `${row.name}[=${row.valueHint}]`;
43334
43218
  return SPACE_FORM_FLAGS.has(row.name) ? `${row.name} ${row.valueHint}` : `${row.name}=${row.valueHint}`;
43335
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
+ ];
43336
43249
  const renderHelp = () => {
43337
- const commandBlocks = COMMAND_ROWS.map((row) => {
43338
- 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}>`;
43339
- return renderBlock(header, row.details);
43340
- });
43341
- return `Usage: gtd [command] [options]\n\nCommands:\n${[
43342
- commandBlocks[0],
43343
- START_SHORT_FORM,
43344
- ...commandBlocks.slice(1),
43345
- VERSION_BLOCK,
43346
- HELP_BLOCK
43347
- ].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
+ };
43348
43299
  };
43349
43300
  const tokenize = (tail) => {
43350
43301
  const positionals = [];
43351
43302
  const byFlag = /* @__PURE__ */ new Map();
43352
43303
  const present = /* @__PURE__ */ new Set();
43353
43304
  let jsonSeen = false;
43305
+ let afterBareJson;
43354
43306
  for (let i = 0; i < tail.length; i++) {
43355
43307
  const original = tail[i];
43356
43308
  const tok = SINGLE_DASH_ALIASES.get(original) ?? original;
43357
- if (!tok.startsWith("--")) {
43309
+ if (!tok.startsWith("-") || tok === "-") {
43310
+ if (tail[i - 1] === "--json") afterBareJson ??= original;
43358
43311
  positionals.push(original);
43359
43312
  continue;
43360
43313
  }
43361
- const row = flagByToken(tok);
43362
- if (row === void 0) return {
43363
- kind: "error",
43364
- message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
43365
- jsonSeen
43366
- };
43314
+ const row = tok.startsWith("--") ? flagByToken(tok) : void 0;
43315
+ if (row === void 0) return unknownOption(tok, jsonSeen);
43367
43316
  const eq = tok.indexOf("=");
43368
43317
  const alreadyPresent = present.has(row.name);
43369
43318
  present.add(row.name);
43370
43319
  if (row.name === "--json") jsonSeen = true;
43371
43320
  if (row.arity === 0) {
43372
- if (eq !== -1) return {
43373
- kind: "error",
43374
- message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
43375
- jsonSeen
43376
- };
43321
+ if (eq !== -1) return unknownOption(tok, jsonSeen);
43377
43322
  if (!byFlag.has(row.name)) byFlag.set(row.name, []);
43378
43323
  continue;
43379
43324
  }
@@ -43424,7 +43369,8 @@ const tokenize = (tail) => {
43424
43369
  positionals,
43425
43370
  byFlag,
43426
43371
  present,
43427
- jsonSeen
43372
+ jsonSeen,
43373
+ ...afterBareJson !== void 0 ? { afterBareJson } : {}
43428
43374
  };
43429
43375
  };
43430
43376
  const usagePlan = (message, json, stdout = "") => ({
@@ -43476,18 +43422,38 @@ const buildLandCommand = (bag) => ({
43476
43422
  ...bag["--cost"] !== void 0 ? { cost: bag["--cost"] } : {},
43477
43423
  ...bag["--model"] !== void 0 ? { model: bag["--model"] } : {}
43478
43424
  });
43479
- const parseArgv = (argv) => {
43480
- 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 {
43481
43438
  kind: "output",
43482
43439
  stdout: renderHelp()
43483
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);
43484
43450
  if (argv.includes("--version") || argv.includes("-V")) return {
43485
43451
  kind: "output",
43486
43452
  stdout: `${GTD_VERSION}\n`
43487
43453
  };
43488
43454
  const tokenized = tokenize(argv.slice(2));
43489
43455
  if (tokenized.kind === "error") return usagePlan(tokenized.message, tokenized.jsonSeen);
43490
- const { positionals, byFlag, present, jsonSeen } = tokenized;
43456
+ const { positionals, byFlag, present, jsonSeen, afterBareJson } = tokenized;
43491
43457
  const first = positionals[0];
43492
43458
  const workflowRaw = byFlag.get("--workflow")?.[0];
43493
43459
  const workflowPresent = workflowRaw !== void 0;
@@ -43495,13 +43461,14 @@ const parseArgv = (argv) => {
43495
43461
  kind: "output",
43496
43462
  stdout: `${GTD_VERSION}\n`
43497
43463
  };
43498
- if (first === "help") return {
43499
- kind: "output",
43500
- stdout: renderHelp()
43501
- };
43464
+ if (first === "help") return helpCommandPlan(positionals.slice(1), jsonSeen);
43502
43465
  const isHistory = first === "history";
43503
43466
  const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
43504
- 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
+ }
43505
43472
  const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
43506
43473
  const selectsStart = workflowPresent && first === void 0;
43507
43474
  const judgeRun = row?.kind === "judge" && positionals[1] === "run";
@@ -43509,15 +43476,18 @@ const parseArgv = (argv) => {
43509
43476
  if (row === void 0 && !selectsStart) {
43510
43477
  const violation = scopeViolation(void 0, present);
43511
43478
  if (violation !== void 0) return usagePlan(violation, jsonSeen);
43512
- 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());
43513
- 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);
43514
43481
  }
43515
43482
  const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun || isHistory ? 2 : 1);
43516
43483
  if (kind === "start" && first === void 0) {
43517
43484
  if (restPositionals.length > 0) return usagePlan(`gtd: too many arguments — expected none, got: ${restPositionals.join(", ")}`, jsonSeen);
43518
43485
  } else {
43519
- const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
43520
- 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
+ }
43521
43491
  }
43522
43492
  const violation = scopeViolation(kind, present);
43523
43493
  if (violation !== void 0) return usagePlan(violation, jsonSeen);
@@ -43559,7 +43529,7 @@ const parseArgv = (argv) => {
43559
43529
  kind: "command",
43560
43530
  command: {
43561
43531
  kind: "door",
43562
- name: restPositionals[0],
43532
+ ...restPositionals[0] !== void 0 ? { name: restPositionals[0] } : {},
43563
43533
  args: restPositionals.slice(1)
43564
43534
  },
43565
43535
  json,