@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -5
- package/dist/chunks/{testkit-CMBAAyRK.mjs → testkit-07Z_ENch.mjs} +431 -446
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +410 -440
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -2
- package/src/cli/Cli.test.ts +133 -52
- package/src/cli/Cli.ts +384 -461
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +67 -4
- package/src/cli/Program.ts +65 -18
- package/src/edge/Edge.ts +1 -1
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flows/factory.ts +3 -1
- package/src/gherkin/index.ts +135 -71
- package/src/gherkin/messages.test.ts +90 -0
- package/src/gherkin/messages.ts +84 -0
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/wire/Land.ts +1 -1
- package/src/workflow/load.ts +6 -6
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -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 —
|
|
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(
|
|
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 —
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
42820
|
-
"
|
|
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:
|
|
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
|
-
|
|
42865
|
-
"
|
|
42866
|
-
"
|
|
42867
|
-
|
|
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:
|
|
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
|
-
|
|
42998
|
-
|
|
42999
|
-
|
|
43000
|
-
|
|
43001
|
-
|
|
43002
|
-
|
|
43003
|
-
|
|
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: "
|
|
43114
|
-
kind: "
|
|
42987
|
+
token: "land",
|
|
42988
|
+
kind: "land",
|
|
43115
42989
|
arity: "none",
|
|
43116
|
-
|
|
43117
|
-
|
|
43118
|
-
|
|
43119
|
-
|
|
43120
|
-
|
|
43121
|
-
|
|
43122
|
-
|
|
43123
|
-
|
|
43124
|
-
|
|
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: "
|
|
43133
|
-
kind: "
|
|
43001
|
+
token: "validate",
|
|
43002
|
+
kind: "validate",
|
|
43134
43003
|
arity: "none",
|
|
43135
|
-
|
|
43136
|
-
|
|
43137
|
-
|
|
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
|
-
|
|
43153
|
-
|
|
43154
|
-
|
|
43155
|
-
|
|
43156
|
-
|
|
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
|
-
|
|
43168
|
-
|
|
43169
|
-
|
|
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: "
|
|
43176
|
-
kind: "
|
|
43030
|
+
token: "abandon",
|
|
43031
|
+
kind: "abandon",
|
|
43177
43032
|
arity: "none",
|
|
43178
|
-
|
|
43179
|
-
|
|
43180
|
-
|
|
43181
|
-
|
|
43182
|
-
|
|
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: "
|
|
43188
|
-
kind: "
|
|
43040
|
+
token: "restore",
|
|
43041
|
+
kind: "restore",
|
|
43189
43042
|
arity: "none",
|
|
43190
|
-
|
|
43191
|
-
|
|
43192
|
-
|
|
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: "
|
|
43202
|
-
kind: "
|
|
43048
|
+
token: "summary",
|
|
43049
|
+
kind: "summary",
|
|
43203
43050
|
arity: "none",
|
|
43204
|
-
|
|
43205
|
-
|
|
43206
|
-
|
|
43207
|
-
|
|
43208
|
-
|
|
43209
|
-
|
|
43210
|
-
|
|
43211
|
-
|
|
43212
|
-
|
|
43213
|
-
|
|
43214
|
-
|
|
43215
|
-
|
|
43216
|
-
|
|
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
|
-
|
|
43228
|
-
|
|
43229
|
-
|
|
43230
|
-
|
|
43231
|
-
|
|
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
|
-
|
|
43247
|
-
|
|
43248
|
-
|
|
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
|
-
|
|
43259
|
-
|
|
43260
|
-
|
|
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
|
-
|
|
43275
|
-
|
|
43276
|
-
|
|
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
|
-
|
|
43295
|
-
|
|
43296
|
-
|
|
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
|
-
|
|
43315
|
-
|
|
43316
|
-
|
|
43317
|
-
|
|
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
|
-
|
|
43323
|
-
const
|
|
43324
|
-
|
|
43325
|
-
|
|
43326
|
-
|
|
43327
|
-
|
|
43328
|
-
const
|
|
43329
|
-
const
|
|
43330
|
-
const
|
|
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
|
|
43338
|
-
const
|
|
43339
|
-
|
|
43340
|
-
|
|
43341
|
-
|
|
43342
|
-
|
|
43343
|
-
|
|
43344
|
-
|
|
43345
|
-
|
|
43346
|
-
|
|
43347
|
-
].join("")
|
|
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
|
|
43480
|
-
|
|
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)
|
|
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 —
|
|
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)
|
|
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,
|