@pmelab/gtd 58.2.0 → 59.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +120 -395
- package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +484 -497
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +3 -42
- package/skills/authoring/SKILL.md +34 -27
- package/src/cli/Cli.test.ts +148 -71
- package/src/cli/Cli.ts +384 -463
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +127 -33
- package/src/cli/Program.ts +70 -25
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flags/index.test.ts +0 -13
- package/src/flags/index.ts +1 -3
- 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/install/Install.test.ts +14 -11
- package/src/install/Install.ts +38 -15
- package/src/install/index.ts +1 -2
- package/src/replay/Replay.test.ts +1 -1
- package/src/replay/index.ts +1 -1
- package/src/step/planStart.ts +6 -1
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/twins/FlowScripts.ts +1 -1
- package/src/wire/Land.ts +1 -1
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +138 -113
- package/src/workflow/load.ts +7 -7
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow/vars.test.ts +30 -3
- package/src/workflow/vars.ts +34 -2
- package/src/workflow-kit/index.ts +1 -1
- package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
- package/dist/chunks/review-Bfaror38.mjs +0 -427
- package/dist/chunks/tail-Bd7L940p.mjs +0 -355
- package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
- package/dist/workflows/cleanup.mjs +0 -36
- package/dist/workflows/feature.mjs +0 -1018
- package/dist/workflows/fix.mjs +0 -41
- package/dist/workflows/review.mjs +0 -45
- package/dist/workflows/rollout.mjs +0 -48
- package/dist/workflows.mjs +0 -40651
- package/src/install/ConfigScaffold.ts +0 -22
- package/src/workflows/cleanup/cleanup-path.feature +0 -94
- package/src/workflows/cleanup/cleanup.feature +0 -315
- package/src/workflows/cleanup/cleanup.test.ts +0 -276
- package/src/workflows/cleanup/cleanup.ts +0 -52
- package/src/workflows/cleanup/index.ts +0 -6
- package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
- package/src/workflows/feature/acceptance-criteria.feature +0 -389
- package/src/workflows/feature/criteria-coverage.feature +0 -102
- package/src/workflows/feature/criteria.test.ts +0 -147
- package/src/workflows/feature/criteria.ts +0 -267
- package/src/workflows/feature/fast-path.feature +0 -93
- package/src/workflows/feature/feature.test.ts +0 -338
- package/src/workflows/feature/feature.ts +0 -156
- package/src/workflows/feature/green.ts +0 -19
- package/src/workflows/feature/index.ts +0 -18
- package/src/workflows/feature/lens-models.feature +0 -69
- package/src/workflows/feature/lens-review.feature +0 -566
- package/src/workflows/feature/naive-acceptance.feature +0 -147
- package/src/workflows/feature/naive-test.feature +0 -263
- package/src/workflows/feature/one-step-land.feature +0 -93
- package/src/workflows/feature/planning.ts +0 -41
- package/src/workflows/feature/refactor-absorbs.feature +0 -131
- package/src/workflows/feature/refactor-first.feature +0 -158
- package/src/workflows/feature/refactor-lens-models.feature +0 -79
- package/src/workflows/feature/refactor-rebuild.feature +0 -273
- package/src/workflows/feature/refactor-review.feature +0 -1528
- package/src/workflows/feature/refactor.test.ts +0 -1220
- package/src/workflows/feature/refactor.ts +0 -368
- package/src/workflows/feature/release-flag.feature +0 -167
- package/src/workflows/feature/release-flag.test.ts +0 -49
- package/src/workflows/feature/release-flag.ts +0 -46
- package/src/workflows/feature/review-gate.feature +0 -241
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
- package/src/workflows/feature/risk-tier.feature +0 -351
- package/src/workflows/feature/seam-rejection.feature +0 -146
- package/src/workflows/feature/seam.feature +0 -297
- package/src/workflows/feature/seam.test.ts +0 -527
- package/src/workflows/feature/seam.ts +0 -426
- package/src/workflows/feature/standards.feature +0 -354
- package/src/workflows/feature/steps.test.ts +0 -65
- package/src/workflows/feature/steps.ts +0 -45
- package/src/workflows/feature/tier-pin.feature +0 -66
- package/src/workflows/feature/tier.test.ts +0 -166
- package/src/workflows/feature/tier.ts +0 -56
- package/src/workflows/fix/fix-lens-origin.feature +0 -114
- package/src/workflows/fix/fix-quality.feature +0 -212
- package/src/workflows/fix/fix.feature +0 -79
- package/src/workflows/fix/fix.test.ts +0 -74
- package/src/workflows/fix/fix.ts +0 -52
- package/src/workflows/fix/index.ts +0 -6
- package/src/workflows/index.ts +0 -47
- package/src/workflows/review/index.ts +0 -6
- package/src/workflows/review/review-door.feature +0 -199
- package/src/workflows/review/review.test.ts +0 -27
- package/src/workflows/review/review.ts +0 -60
- package/src/workflows/rollout/index.ts +0 -6
- package/src/workflows/rollout/rollout.feature +0 -510
- package/src/workflows/rollout/rollout.test.ts +0 -263
- package/src/workflows/rollout/rollout.ts +0 -82
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
- package/src/workflows/shared/access.test.ts +0 -62
- package/src/workflows/shared/access.ts +0 -24
- package/src/workflows/shared/config.fixture.ts +0 -12
- package/src/workflows/shared/fences.ts +0 -24
- package/src/workflows/shared/health.test.ts +0 -148
- package/src/workflows/shared/health.ts +0 -191
- package/src/workflows/shared/learn.test.ts +0 -140
- package/src/workflows/shared/learn.ts +0 -201
- package/src/workflows/shared/lenses.test.ts +0 -39
- package/src/workflows/shared/lenses.ts +0 -294
- package/src/workflows/shared/options.test.ts +0 -157
- package/src/workflows/shared/options.ts +0 -92
- package/src/workflows/shared/progress.feature +0 -124
- package/src/workflows/shared/prose.ts +0 -263
- package/src/workflows/shared/recheck.feature +0 -60
- package/src/workflows/shared/registry.test.ts +0 -345
- package/src/workflows/shared/registry.ts +0 -183
- package/src/workflows/shared/reserved.ts +0 -11
- package/src/workflows/shared/review.test.ts +0 -1062
- package/src/workflows/shared/review.ts +0 -446
- package/src/workflows/shared/skills.test.ts +0 -121
- package/src/workflows/shared/skills.ts +0 -49
- package/src/workflows/shared/standards.test.ts +0 -90
- package/src/workflows/shared/standards.ts +0 -156
- package/src/workflows/shared/steps.test.ts +0 -219
- package/src/workflows/shared/steps.ts +0 -201
- package/src/workflows/shared/tail.test.ts +0 -454
- package/src/workflows/shared/tail.ts +0 -264
- package/src/workflows/shared/text.fixture.ts +0 -128
- package/src/workflows/shared/text.test.ts +0 -1430
- package/src/workflows/shared/text.ts +0 -1700
- package/src/workflows/shared/threads.test.ts +0 -129
- package/src/workflows/shared/vars.ts +0 -47
- package/src/workflows/shared/workflows.steps.ts +0 -54
package/src/cli/Cli.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from "./Program.js"
|
|
37
37
|
import type { ToolCommands } from "./ToolCommands.js"
|
|
38
38
|
import { THRESHOLDS } from "./Judges.js"
|
|
39
|
+
import { closest, definition, wrap } from "./Help.js"
|
|
39
40
|
import { EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR } from "../wire/index.js"
|
|
40
41
|
|
|
41
42
|
export type { Command, CommandRequirements } from "./Program.js"
|
|
@@ -77,8 +78,9 @@ interface FlagRow {
|
|
|
77
78
|
readonly decode: (raws: readonly string[]) => Either.Either<unknown, string>
|
|
78
79
|
readonly scopeError: string
|
|
79
80
|
readonly valueHint: string
|
|
80
|
-
/**
|
|
81
|
-
readonly help:
|
|
81
|
+
/** One sentence for a command's help, wrapped when rendered; `helpFor` overrides it for a command the flag means something else on. */
|
|
82
|
+
readonly help: string
|
|
83
|
+
readonly helpFor?: Partial<Record<Command["kind"], string>>
|
|
82
84
|
/** Flag names this one may never appear alongside — checked generically after parsing (see `conflictViolation`), never as a bespoke `if`. Defaults to none. */
|
|
83
85
|
readonly conflicts?: readonly string[]
|
|
84
86
|
}
|
|
@@ -115,20 +117,10 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
115
117
|
"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` — " +
|
|
116
118
|
"every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
117
119
|
valueHint: "<path>",
|
|
118
|
-
help:
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
"kind, content, session.id) prints just that value: a",
|
|
123
|
-
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
124
|
-
"one JSON entry per line — a numeric segment (e.g.",
|
|
125
|
-
"skills.0) indexes into one entry of a list field instead.",
|
|
126
|
-
"An absent optional field prints nothing and exits 0 —",
|
|
127
|
-
"including when an earlier segment of <path> is itself",
|
|
128
|
-
"absent/null (e.g. session.id at a non-prompt rest), which",
|
|
129
|
-
"never counts as unknown; an unknown path is a usage error",
|
|
130
|
-
"(exit 2).",
|
|
131
|
-
],
|
|
120
|
+
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",
|
|
121
|
+
helpFor: {
|
|
122
|
+
strays: "Print one entry of the array (--json=0), nothing past its end",
|
|
123
|
+
},
|
|
132
124
|
},
|
|
133
125
|
{
|
|
134
126
|
name: "--port",
|
|
@@ -143,11 +135,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
143
135
|
},
|
|
144
136
|
scopeError: "gtd: --port is only valid for `gtd ui`",
|
|
145
137
|
valueHint: "<n>",
|
|
146
|
-
help:
|
|
147
|
-
"(gtd ui only) the tailscale serve port (default: the first",
|
|
148
|
-
"free of 8443, 10000, 443), or the bind port when --host",
|
|
149
|
-
"opts out of serve (default: a free port)",
|
|
150
|
-
],
|
|
138
|
+
help: "The tailscale serve port (default: the first free of 8443, 10000, 443), or with --host the port to bind (default: any free one)",
|
|
151
139
|
},
|
|
152
140
|
{
|
|
153
141
|
name: "--host",
|
|
@@ -160,11 +148,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
160
148
|
: Either.right(raw),
|
|
161
149
|
scopeError: "gtd: --host is only valid for `gtd ui`",
|
|
162
150
|
valueHint: "<addr>",
|
|
163
|
-
help:
|
|
164
|
-
"(gtd ui only) opt out of the default tailscale serve front",
|
|
165
|
-
"door and bind this address directly instead, showing it in",
|
|
166
|
-
"the printed URL",
|
|
167
|
-
],
|
|
151
|
+
help: "Bind this address directly instead of publishing through tailscale serve",
|
|
168
152
|
},
|
|
169
153
|
{
|
|
170
154
|
name: "--self-signed",
|
|
@@ -174,10 +158,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
174
158
|
decode: () => Either.right(true),
|
|
175
159
|
scopeError: "gtd: --self-signed is only valid for `gtd ui`",
|
|
176
160
|
valueHint: "",
|
|
177
|
-
help:
|
|
178
|
-
"(gtd ui only) generate a throwaway self-signed TLS",
|
|
179
|
-
"certificate instead of the configured ui.cert/ui.key",
|
|
180
|
-
],
|
|
161
|
+
help: "Bind directly with a throwaway self-signed certificate instead of the configured ui.cert/ui.key",
|
|
181
162
|
},
|
|
182
163
|
{
|
|
183
164
|
name: "--dev",
|
|
@@ -187,7 +168,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
187
168
|
decode: () => Either.right(true),
|
|
188
169
|
scopeError: "gtd: --dev is only valid for `gtd ui`",
|
|
189
170
|
valueHint: "",
|
|
190
|
-
help:
|
|
171
|
+
help: "Serve local development sources instead of the packaged build",
|
|
191
172
|
},
|
|
192
173
|
{
|
|
193
174
|
name: "--provider",
|
|
@@ -200,11 +181,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
200
181
|
: Either.left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
|
|
201
182
|
scopeError: "gtd: --provider is only valid for `gtd judge run`",
|
|
202
183
|
valueHint: "<name>",
|
|
203
|
-
help:
|
|
204
|
-
"(gtd judge run only) the answerer: fixed | jev | llm;",
|
|
205
|
-
"omitted: GTD_JUDGE_PROVIDER, then judge.json, then jev",
|
|
206
|
-
"when TYPESAFE_API_KEY is set, else llm",
|
|
207
|
-
],
|
|
184
|
+
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)",
|
|
208
185
|
},
|
|
209
186
|
{
|
|
210
187
|
name: "--answers",
|
|
@@ -217,11 +194,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
217
194
|
: Either.right(raw),
|
|
218
195
|
scopeError: "gtd: --answers is only valid for `gtd judge run`",
|
|
219
196
|
valueHint: "<path>",
|
|
220
|
-
help: [
|
|
221
|
-
"(gtd judge run --provider fixed only) a JSON verdict array",
|
|
222
|
-
"[{ id, answer, p }]; else the GTD_JUDGE_ANSWERS env var",
|
|
223
|
-
"(inline JSON) is read",
|
|
224
|
-
],
|
|
197
|
+
help: "With --provider fixed: a JSON verdict file [{ id, answer, p }] (default: $GTD_JUDGE_ANSWERS, inline JSON)",
|
|
225
198
|
},
|
|
226
199
|
{
|
|
227
200
|
name: "--cost",
|
|
@@ -231,7 +204,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
231
204
|
decode: ([raw]) => nonNegativeNumber(raw ?? "", "--cost"),
|
|
232
205
|
scopeError: "gtd: --cost is only valid for `gtd land` — a start is not a metered agent turn",
|
|
233
206
|
valueHint: "<n>",
|
|
234
|
-
help:
|
|
207
|
+
help: "Record the token cost of the invocation that just finished on the commit",
|
|
235
208
|
},
|
|
236
209
|
{
|
|
237
210
|
name: "--model",
|
|
@@ -244,11 +217,12 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
244
217
|
: Either.right(raw),
|
|
245
218
|
scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
|
|
246
219
|
valueHint: "<name>",
|
|
247
|
-
help:
|
|
248
|
-
|
|
249
|
-
"
|
|
250
|
-
|
|
251
|
-
|
|
220
|
+
help: "",
|
|
221
|
+
helpFor: {
|
|
222
|
+
land: "With --cost: the model that cost was spent on",
|
|
223
|
+
judgeRun:
|
|
224
|
+
"The claude model the llm answerer uses (default: $GTD_JUDGE_MODEL, then judge.json, then sonnet)",
|
|
225
|
+
},
|
|
252
226
|
},
|
|
253
227
|
{
|
|
254
228
|
name: "--workflow",
|
|
@@ -260,10 +234,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
260
234
|
"gtd: --workflow is only valid with no other command — use the bare `gtd --workflow <name>` " +
|
|
261
235
|
"form; landing and starting are different verbs",
|
|
262
236
|
valueHint: "<name>",
|
|
263
|
-
help:
|
|
264
|
-
"(with no command at all) start a brand new process on",
|
|
265
|
-
"the named workflow — authenticated as human",
|
|
266
|
-
],
|
|
237
|
+
help: "Start a new process on the named workflow",
|
|
267
238
|
},
|
|
268
239
|
{
|
|
269
240
|
name: "--var",
|
|
@@ -308,14 +279,12 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
308
279
|
},
|
|
309
280
|
scopeError: "gtd: --var requires --workflow (or `gtd history judges`)",
|
|
310
281
|
valueHint: "<name>=<value>",
|
|
311
|
-
help:
|
|
312
|
-
|
|
313
|
-
"
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
"qualityTriageMinP",
|
|
318
|
-
],
|
|
282
|
+
help: "",
|
|
283
|
+
helpFor: {
|
|
284
|
+
start: "Pin one of the workflow's declared settings for the new process (repeatable)",
|
|
285
|
+
historyJudges:
|
|
286
|
+
"Re-decide every past verdict under this threshold: judgeIdenticalMinP or qualityTriageMinP (repeatable)",
|
|
287
|
+
},
|
|
319
288
|
},
|
|
320
289
|
{
|
|
321
290
|
name: "--jsonl",
|
|
@@ -325,7 +294,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
325
294
|
decode: () => Either.right(true),
|
|
326
295
|
scopeError: "gtd: --jsonl is only valid for `gtd history judges`",
|
|
327
296
|
valueHint: "",
|
|
328
|
-
help:
|
|
297
|
+
help: "Print one JSON line per verdict instead of the report",
|
|
329
298
|
conflicts: ["--json", "--var"],
|
|
330
299
|
},
|
|
331
300
|
{
|
|
@@ -336,12 +305,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
336
305
|
decode: () => Either.right(true),
|
|
337
306
|
scopeError: "gtd: --open-questions is only valid for `gtd check`",
|
|
338
307
|
valueHint: "",
|
|
339
|
-
help:
|
|
340
|
-
"(gtd check only) ignore <mode>'s structural findings and",
|
|
341
|
-
"instead run the qa open-questions predicate over <file>,",
|
|
342
|
-
"printing each unanswered question one per line and exiting",
|
|
343
|
-
"non-zero when any remain",
|
|
344
|
-
],
|
|
308
|
+
help: "Instead of <mode>'s findings, list the unanswered questions in <file>; exits 1 when any remain",
|
|
345
309
|
},
|
|
346
310
|
{
|
|
347
311
|
name: "--open-threads",
|
|
@@ -351,15 +315,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
351
315
|
decode: () => Either.right(true),
|
|
352
316
|
scopeError: "gtd: --open-threads is only valid for `gtd check`",
|
|
353
317
|
valueHint: "",
|
|
354
|
-
help:
|
|
355
|
-
"(gtd check only) ignore <mode>'s structural findings and",
|
|
356
|
-
"instead list every open thread (a footnote whose last",
|
|
357
|
-
"entry is the agent's) over <file>, one per line with its",
|
|
358
|
-
"first H entry, exiting non-zero when any are open. With no",
|
|
359
|
-
"<mode>/<file>, lists code-comment threads in the files the",
|
|
360
|
-
"current process changed (read from its diff base, unlike",
|
|
361
|
-
"the rest of `gtd check`)",
|
|
362
|
-
],
|
|
318
|
+
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",
|
|
363
319
|
},
|
|
364
320
|
{
|
|
365
321
|
name: "--verbose",
|
|
@@ -369,11 +325,7 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
369
325
|
decode: () => Either.right(true),
|
|
370
326
|
scopeError: "gtd: --verbose is valid for every command — this error is unreachable",
|
|
371
327
|
valueHint: "",
|
|
372
|
-
help:
|
|
373
|
-
"enable stderr narration for this invocation: which rest",
|
|
374
|
-
"resolved and how config resolved across layers. Aliased",
|
|
375
|
-
"to -v",
|
|
376
|
-
],
|
|
328
|
+
help: "Narrate on stderr what gtd resolved and where its config came from",
|
|
377
329
|
},
|
|
378
330
|
]
|
|
379
331
|
|
|
@@ -409,448 +361,373 @@ type Arity =
|
|
|
409
361
|
// Any number of positionals, none included.
|
|
410
362
|
| { readonly name: string; readonly many: true }
|
|
411
363
|
|
|
364
|
+
type Group = "Core loop" | "Start and stop" | "Inspect" | "Driver plumbing"
|
|
365
|
+
|
|
412
366
|
interface CommandRow {
|
|
413
367
|
readonly token: string
|
|
414
368
|
readonly kind: Command["kind"]
|
|
415
369
|
readonly arity: Arity
|
|
416
|
-
readonly
|
|
370
|
+
readonly group: Group
|
|
371
|
+
/** The top-level help's one line — short enough for its column. */
|
|
372
|
+
readonly summary: string
|
|
373
|
+
/** The command's own help: prose paragraphs, wrapped when rendered (see `wrap`). */
|
|
374
|
+
readonly details: string
|
|
417
375
|
}
|
|
418
376
|
|
|
419
377
|
const COMMAND_ROWS: readonly CommandRow[] = [
|
|
420
378
|
{
|
|
421
|
-
token: "
|
|
422
|
-
kind: "
|
|
423
|
-
arity: "none",
|
|
424
|
-
details: [
|
|
425
|
-
"Land whatever the tree now shows at the currently resolved",
|
|
426
|
-
"rest — a human capture, an agent/check turn, or an empty",
|
|
427
|
-
"attempt (a fruitless prompt turn). Pass",
|
|
428
|
-
"--cost=<n> (optionally --model=<name>) to record the",
|
|
429
|
-
"just-finished invocation's token cost and model on the",
|
|
430
|
-
"turn commit (summed into it.processCost/",
|
|
431
|
-
"processCostByModel). Plain (the default) prints one",
|
|
432
|
-
"human-readable sentence naming the commit subject (or a",
|
|
433
|
-
"no-op note) plus a pointer to `gtd land --json=script | sh`",
|
|
434
|
-
"to get the landing script — never the script itself.",
|
|
435
|
-
"--json emits script (the actual POSIX sh",
|
|
436
|
-
"a driver runs) alongside settled, idle, state (the",
|
|
437
|
-
"post-land target), subject, cost and model. Exits 0 on",
|
|
438
|
-
"success, 1 on any refusal — see the Exit codes section",
|
|
439
|
-
"below",
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
token: "abandon",
|
|
444
|
-
kind: "abandon",
|
|
445
|
-
arity: "none",
|
|
446
|
-
details: [
|
|
447
|
-
"End the process currently underway without completing it:",
|
|
448
|
-
"rewind HEAD to the commit the process started from,",
|
|
449
|
-
"keeping everything it produced as uncommitted changes. A",
|
|
450
|
-
"no-op when no process is underway",
|
|
451
|
-
],
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
token: "restore",
|
|
455
|
-
kind: "restore",
|
|
379
|
+
token: "next",
|
|
380
|
+
kind: "next",
|
|
456
381
|
arity: "none",
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
],
|
|
382
|
+
group: "Core loop",
|
|
383
|
+
summary: "Print what should happen next",
|
|
384
|
+
details: `Print what should happen next in the process, changing nothing — safe to poll.
|
|
385
|
+
|
|
386
|
+
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.
|
|
387
|
+
|
|
388
|
+
--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).`,
|
|
465
389
|
},
|
|
466
390
|
{
|
|
467
|
-
token: "
|
|
468
|
-
kind: "
|
|
391
|
+
token: "land",
|
|
392
|
+
kind: "land",
|
|
469
393
|
arity: "none",
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
"are the agent's own input. --json emits the one structured",
|
|
480
|
-
"surface gtd has: kind (capture|message|script|prompt|",
|
|
481
|
-
"stalled) selects what a driver does, content is what it",
|
|
482
|
-
"runs or shows, idle marks the workflow's initial state with",
|
|
483
|
-
"a clean tree, plus the prompt session, model, validate",
|
|
484
|
-
"script, log path, changes, next and the resting state's own",
|
|
485
|
-
"fields. --json=<path> (a dotted key path into that same",
|
|
486
|
-
"document, e.g. kind, content, session.id) prints just that",
|
|
487
|
-
"value instead of the whole document — see --json's own help",
|
|
488
|
-
"above. Exits 0 — see the Exit codes section below",
|
|
489
|
-
],
|
|
394
|
+
group: "Core loop",
|
|
395
|
+
summary: "Record what the tree now shows",
|
|
396
|
+
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.
|
|
397
|
+
|
|
398
|
+
gtd writes nothing itself. Plain output names the commit it would make; --json=script prints the script that makes it:
|
|
399
|
+
|
|
400
|
+
s=$(gtd land --json=script) && sh -c "$s"
|
|
401
|
+
|
|
402
|
+
--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.`,
|
|
490
403
|
},
|
|
491
404
|
{
|
|
492
405
|
token: "validate",
|
|
493
406
|
kind: "validate",
|
|
494
407
|
arity: "none",
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
"mode's commands (its file:/mode:), instead of running it —",
|
|
499
|
-
"a driver runs the script and reads the findings from its",
|
|
500
|
-
'own exit code/output. Always exits 0; prints "nothing to',
|
|
501
|
-
'validate" when the resolved rest declares nothing to run.',
|
|
502
|
-
"On a non-zero validate exit the emitted script prints a",
|
|
503
|
-
"ready-to-send fix prompt (instruction + findings) and",
|
|
504
|
-
"exits with the validator's own code",
|
|
505
|
-
],
|
|
408
|
+
group: "Core loop",
|
|
409
|
+
summary: "Print the script that checks the steering file",
|
|
410
|
+
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.`,
|
|
506
411
|
},
|
|
507
412
|
{
|
|
508
|
-
token: "
|
|
509
|
-
kind: "
|
|
510
|
-
arity: "
|
|
511
|
-
|
|
413
|
+
token: "door",
|
|
414
|
+
kind: "door",
|
|
415
|
+
arity: { name: "name", variadic: "args" },
|
|
416
|
+
group: "Start and stop",
|
|
417
|
+
summary: "Start a process through a named shortcut",
|
|
418
|
+
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.
|
|
419
|
+
|
|
420
|
+
With no <name>, an unknown one, or the wrong number of args, it lists the doors and exits 2.`,
|
|
512
421
|
},
|
|
513
422
|
{
|
|
514
|
-
token: "
|
|
515
|
-
kind: "
|
|
423
|
+
token: "doors",
|
|
424
|
+
kind: "doors",
|
|
516
425
|
arity: "none",
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
"refuses outside a repository like every other state command.",
|
|
521
|
-
"By default, publishes through `tailscale serve` (reachable",
|
|
522
|
-
"from anywhere on the tailnet, including over a DERP relay)",
|
|
523
|
-
"with tailscaled terminating TLS, walking 8443, then 10000,",
|
|
524
|
-
"then 443 until one publishes; falls back to binding a local",
|
|
525
|
-
"HTTPS server directly, never refusing, when none of the",
|
|
526
|
-
"three works. --host <addr> opts out of serve and binds that",
|
|
527
|
-
"address directly instead; --port <n> overrides the serve",
|
|
528
|
-
"port (default: the first free of 8443, 10000, 443), or the",
|
|
529
|
-
"bind port when --host is given (default: a free port);",
|
|
530
|
-
"--self-signed also opts out of serve, generating a",
|
|
531
|
-
"throwaway TLS certificate instead of the configured",
|
|
532
|
-
"ui.cert/ui.key; --dev runs against local development sources",
|
|
533
|
-
"instead of the packaged build",
|
|
534
|
-
],
|
|
426
|
+
group: "Start and stop",
|
|
427
|
+
summary: "List the doors",
|
|
428
|
+
details: `List every door, sorted: one \`name [args] → workflow\` line each. --json prints a JSON array of { name, workflow, args: [{ name, optional }] }. Writes nothing.`,
|
|
535
429
|
},
|
|
536
430
|
{
|
|
537
|
-
token: "
|
|
538
|
-
kind: "
|
|
539
|
-
arity:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
"state and reads no config — standalone, runnable from any",
|
|
546
|
-
"directory with <mode>/<file> given explicitly. This is what a",
|
|
547
|
-
"workflow's emitted validation script invokes as a leaf step.",
|
|
548
|
-
"--open-questions runs the qa unanswered-questions predicate",
|
|
549
|
-
"instead (see --help); --open-threads lists open threads.",
|
|
550
|
-
"`gtd check --open-threads` alone lists open code-comment",
|
|
551
|
-
"threads (`// H: ...`) in the files the process changed;",
|
|
552
|
-
"it prints nothing when no process is underway.",
|
|
553
|
-
],
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
token: "uncheck",
|
|
557
|
-
kind: "uncheck",
|
|
558
|
-
arity: { name: "file" },
|
|
559
|
-
details: [
|
|
560
|
-
"Read <file> and reset every review-mode `- [x]`/`- [X]`",
|
|
561
|
-
"file-pointer box back to `- [ ]`, writing the result back",
|
|
562
|
-
"only when the bytes actually changed. Resolves no workflow",
|
|
563
|
-
"state and reads no config — standalone, runnable from any",
|
|
564
|
-
"directory with <file> given explicitly. This is what the",
|
|
565
|
-
"landing script runs ahead of the commit at the human review",
|
|
566
|
-
"gate, so no tick ever reaches a commit. Takes no <mode> —",
|
|
567
|
-
"it means review-mode file pointers and nothing else, never",
|
|
568
|
-
"qa-mode's answered-question boxes. A missing file writes",
|
|
569
|
-
"nothing and exits 0",
|
|
570
|
-
],
|
|
431
|
+
token: "abandon",
|
|
432
|
+
kind: "abandon",
|
|
433
|
+
arity: "none",
|
|
434
|
+
group: "Start and stop",
|
|
435
|
+
summary: "Stop the process, keeping its changes",
|
|
436
|
+
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.
|
|
437
|
+
|
|
438
|
+
s=$(gtd abandon) && sh -c "$s"`,
|
|
571
439
|
},
|
|
572
440
|
{
|
|
573
|
-
token: "
|
|
574
|
-
kind: "
|
|
441
|
+
token: "restore",
|
|
442
|
+
kind: "restore",
|
|
575
443
|
arity: "none",
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
"runtime — the self-serve version of",
|
|
580
|
-
"https://github.com/pmelab/gtd/blob/main/docs/driver.md's",
|
|
581
|
-
"'Writing your own driver'. At a repository root no",
|
|
582
|
-
"gtd.config.ts governs yet, it also writes one importing each",
|
|
583
|
-
"shipped workflow from @pmelab/gtd/workflows/<name>; anywhere",
|
|
584
|
-
"else it writes nothing.",
|
|
585
|
-
],
|
|
444
|
+
group: "Start and stop",
|
|
445
|
+
summary: "Bring back the last abandoned process",
|
|
446
|
+
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.`,
|
|
586
447
|
},
|
|
587
448
|
{
|
|
588
449
|
token: "summary",
|
|
589
450
|
kind: "summary",
|
|
590
451
|
arity: "none",
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
"it.processCost/processCostByModel. Writes nothing: no git,",
|
|
597
|
-
"no state transition, no file, no session identity — the",
|
|
598
|
-
"driver pipes the output to a cold agent and does what it",
|
|
599
|
-
"wants with the result (a squash, an amend, a PR body).",
|
|
600
|
-
"Refuses (exit 1) when the workflow declares no summary:",
|
|
601
|
-
"template, or when the resolved run has no commits to name —",
|
|
602
|
-
"it is runnable any time before the next thing lands on the",
|
|
603
|
-
"branch",
|
|
604
|
-
],
|
|
452
|
+
group: "Inspect",
|
|
453
|
+
summary: "Print the prompt for the closing message",
|
|
454
|
+
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.
|
|
455
|
+
|
|
456
|
+
Refuses when the workflow declares no summary template, or when the process has no commits.`,
|
|
605
457
|
},
|
|
606
458
|
{
|
|
607
459
|
token: "base",
|
|
608
460
|
kind: "base",
|
|
609
461
|
arity: "none",
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
"bare, newline-terminated, and nothing else. Writes nothing:",
|
|
614
|
-
"no git, no state transition, no session identity. Before",
|
|
615
|
-
"the first review round it's the process's diff base;",
|
|
616
|
-
"afterward it's the most-recent review round's boundary.",
|
|
617
|
-
"Refuses (exit 1) when no process is underway.",
|
|
618
|
-
],
|
|
619
|
-
},
|
|
620
|
-
{
|
|
621
|
-
token: "door",
|
|
622
|
-
kind: "door",
|
|
623
|
-
arity: { name: "name", variadic: "args" },
|
|
624
|
-
details: [
|
|
625
|
-
"Start a process through a named shortcut, mapping the",
|
|
626
|
-
"positional args to that workflow's process settings — the",
|
|
627
|
-
"same start `gtd --workflow` makes, printed the same way.",
|
|
628
|
-
"Each workflow the config exports brings its own doors (gtd's",
|
|
629
|
-
"`fix`, `review [base]`, `rollout <flag>` and",
|
|
630
|
-
"`cleanup <flag>`), plus the config's `doors` export. An",
|
|
631
|
-
"unknown door, or too few or too many args, is a usage error",
|
|
632
|
-
"(exit 2) naming the doors or the door's synopsis",
|
|
633
|
-
],
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
token: "doors",
|
|
637
|
-
kind: "doors",
|
|
638
|
-
arity: "none",
|
|
639
|
-
details: [
|
|
640
|
-
"List every door, sorted: one `name [args] → workflow` line",
|
|
641
|
-
"each. --json prints one JSON array of { name, workflow,",
|
|
642
|
-
"args: [{ name, optional }] } instead; --json=<path> reads one",
|
|
643
|
-
"value off it. Writes nothing",
|
|
644
|
-
],
|
|
645
|
-
},
|
|
646
|
-
{
|
|
647
|
-
token: "judge",
|
|
648
|
-
kind: "judge",
|
|
649
|
-
arity: "none",
|
|
650
|
-
details: [
|
|
651
|
-
"Print the resolved rest's pending judgment — the prepared",
|
|
652
|
-
"state, its typed questions, and their criteria — the same",
|
|
653
|
-
"judge field `gtd next --json` already carries. Read-only:",
|
|
654
|
-
"resolves no session, writes nothing. Refuses (exit 1) when",
|
|
655
|
-
"the resolved rest declares no judge:",
|
|
656
|
-
],
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
token: "judge answer",
|
|
660
|
-
kind: "judgeAnswer",
|
|
661
|
-
arity: "none",
|
|
662
|
-
details: [
|
|
663
|
-
"Read a verdict off stdin — one { id, answer, p } entry per",
|
|
664
|
-
"question the pending judgment declared — and decode it",
|
|
665
|
-
"against an Effect Schema built from those same question",
|
|
666
|
-
"ids. Refuses (exit 1) when the resolved rest declares no",
|
|
667
|
-
"judge:. Exits 2 (a usage error, like an unknown --json",
|
|
668
|
-
"selector) when stdin isn't valid JSON or the verdict",
|
|
669
|
-
"doesn't decode against the pending question ids.",
|
|
670
|
-
],
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
token: "judge run",
|
|
674
|
-
kind: "judgeRun",
|
|
675
|
-
arity: "none",
|
|
676
|
-
details: [
|
|
677
|
-
"Answer a judgment: read the `gtd judge --json` document off",
|
|
678
|
-
"stdin, write a verdict [{ id, answer, p }] on stdout — what",
|
|
679
|
-
"`gtd judge answer` decodes. --provider fixed answers from",
|
|
680
|
-
"--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
|
|
681
|
-
"does not cover are left out. --provider jev asks TypeSafe",
|
|
682
|
-
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
683
|
-
"on PATH (Claude Code only), model sonnet unless --model.",
|
|
684
|
-
"No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
|
|
685
|
-
"judge.json, else jev when TYPESAFE_API_KEY is set and no",
|
|
686
|
-
"model is given, else llm; --model likewise falls back to",
|
|
687
|
-
"GTD_JUDGE_MODEL, then the file's model. Needs no",
|
|
688
|
-
"repository. jev and llm exit 1 when they cannot answer",
|
|
689
|
-
"every question, with nothing on stdout",
|
|
690
|
-
],
|
|
462
|
+
group: "Inspect",
|
|
463
|
+
summary: "Print the commit a review diff starts from",
|
|
464
|
+
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.`,
|
|
691
465
|
},
|
|
692
466
|
{
|
|
693
467
|
token: "strays",
|
|
694
468
|
kind: "strays",
|
|
695
469
|
arity: { name: "range", variadic: "glob" },
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
"globs in the shell. stdout: a sorted JSON array, [] when",
|
|
702
|
-
"clean; stderr: a summary, plus one stray: line per path with",
|
|
703
|
-
"--verbose. --json=<n> picks one entry, nothing past the end;",
|
|
704
|
-
"a symlink is a path like any other, never followed; a",
|
|
705
|
-
"submodule counts as its path, whatever diff.ignoreSubmodules",
|
|
706
|
-
"or .gitmodules ignore say. Runs in any git repository, no",
|
|
707
|
-
"process needed. Exits 10 when",
|
|
708
|
-
"strays were found, 1 on an unknown revision, 2 on a bad range",
|
|
709
|
-
],
|
|
470
|
+
group: "Inspect",
|
|
471
|
+
summary: "List changed files outside the globs",
|
|
472
|
+
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.
|
|
473
|
+
|
|
474
|
+
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.`,
|
|
710
475
|
},
|
|
711
476
|
{
|
|
712
477
|
token: "history list",
|
|
713
478
|
kind: "historyList",
|
|
714
479
|
arity: "none",
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
"--json prints one array of { ref, entry, branch, tip, date,",
|
|
719
|
-
"subject }. Writes nothing and never replays; with no refs it",
|
|
720
|
-
"hints at `gtd history fetch | sh` and still exits 0",
|
|
721
|
-
],
|
|
480
|
+
group: "Inspect",
|
|
481
|
+
summary: "List finished processes",
|
|
482
|
+
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\`.`,
|
|
722
483
|
},
|
|
723
484
|
{
|
|
724
485
|
token: "history show",
|
|
725
486
|
kind: "historyShow",
|
|
726
487
|
arity: { name: "ref" },
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
"entry hash (or unique prefix) or a branch name. Refuses",
|
|
731
|
-
"(exit 1) when a config file differs from its content at the",
|
|
732
|
-
"ref's tip. A version mismatch only warns on stderr. --json",
|
|
733
|
-
"prints the trail as one document; writes nothing",
|
|
734
|
-
],
|
|
488
|
+
group: "Inspect",
|
|
489
|
+
summary: "Replay one finished process",
|
|
490
|
+
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.`,
|
|
735
491
|
},
|
|
736
492
|
{
|
|
737
493
|
token: "history metrics",
|
|
738
494
|
kind: "historyMetrics",
|
|
739
495
|
arity: { name: "ref", many: true },
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
"model and phase with the double work of a rebuild, quality-lap",
|
|
744
|
-
"findings per lens, and red checks that passed on their",
|
|
745
|
-
"re-check — per process and in total. Each <ref>",
|
|
746
|
-
"is read as by history show, but a branch name means every",
|
|
747
|
-
"process kept from it; none means every ref.",
|
|
748
|
-
"Reads commit subjects and trailers, never replays, so a",
|
|
749
|
-
"changed config is no refusal. --json prints { processes, total",
|
|
750
|
-
"} as one document; writes nothing",
|
|
751
|
-
],
|
|
496
|
+
group: "Inspect",
|
|
497
|
+
summary: "Report cost and rework of finished processes",
|
|
498
|
+
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.`,
|
|
752
499
|
},
|
|
753
500
|
{
|
|
754
501
|
token: "history judges",
|
|
755
502
|
kind: "historyJudges",
|
|
756
503
|
arity: { name: "ref", many: true },
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
"and p bucket, how many verdicts later events overturned.",
|
|
761
|
-
"--var <name>=<value> re-decides every past verdict under",
|
|
762
|
-
"another threshold and prints what would have changed;",
|
|
763
|
-
"--jsonl prints one { process, judge, commit, step, question,",
|
|
764
|
-
"state, answer, p, outcome } line per verdict instead. <ref>",
|
|
765
|
-
"is read as by history metrics. Never replays and asks no",
|
|
766
|
-
"model. --json prints { processes, total, whatIf }; writes",
|
|
767
|
-
"nothing",
|
|
768
|
-
],
|
|
504
|
+
group: "Inspect",
|
|
505
|
+
summary: "Calibrate the judges of finished processes",
|
|
506
|
+
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.`,
|
|
769
507
|
},
|
|
770
508
|
{
|
|
771
509
|
token: "history fetch",
|
|
772
510
|
kind: "historyFetch",
|
|
773
511
|
arity: { name: "remote", optional: true },
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
512
|
+
group: "Inspect",
|
|
513
|
+
summary: "Print a script that fetches finished processes",
|
|
514
|
+
details: `Print a script that fetches every finished process kept on <remote> (default: origin) and reports how many arrived. gtd runs no git itself:
|
|
515
|
+
|
|
516
|
+
s=$(gtd history fetch) && sh -c "$s"`,
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
token: "ui",
|
|
520
|
+
kind: "ui",
|
|
521
|
+
arity: "none",
|
|
522
|
+
group: "Inspect",
|
|
523
|
+
summary: "Serve the web and phone client",
|
|
524
|
+
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.`,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
token: "install",
|
|
528
|
+
kind: "install",
|
|
529
|
+
arity: "none",
|
|
530
|
+
group: "Driver plumbing",
|
|
531
|
+
summary: "Print a briefing for writing a driver",
|
|
532
|
+
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.`,
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
token: "judge",
|
|
536
|
+
kind: "judge",
|
|
537
|
+
arity: "none",
|
|
538
|
+
group: "Driver plumbing",
|
|
539
|
+
summary: "Print the pending judgment",
|
|
540
|
+
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.`,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
token: "judge answer",
|
|
544
|
+
kind: "judgeAnswer",
|
|
545
|
+
arity: "none",
|
|
546
|
+
group: "Driver plumbing",
|
|
547
|
+
summary: "Land a verdict read from stdin",
|
|
548
|
+
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).`,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
token: "judge run",
|
|
552
|
+
kind: "judgeRun",
|
|
553
|
+
arity: "none",
|
|
554
|
+
group: "Driver plumbing",
|
|
555
|
+
summary: "Answer a judgment with a model",
|
|
556
|
+
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.
|
|
557
|
+
|
|
558
|
+
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.`,
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
token: "check",
|
|
562
|
+
kind: "check",
|
|
563
|
+
arity: { names: ["mode", "file"] },
|
|
564
|
+
group: "Driver plumbing",
|
|
565
|
+
summary: "Check a steering file's format",
|
|
566
|
+
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.`,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
token: "uncheck",
|
|
570
|
+
kind: "uncheck",
|
|
571
|
+
arity: { name: "file" },
|
|
572
|
+
group: "Driver plumbing",
|
|
573
|
+
summary: "Untick a review file's boxes",
|
|
574
|
+
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.`,
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
token: "lsp",
|
|
578
|
+
kind: "lsp",
|
|
579
|
+
arity: "none",
|
|
580
|
+
group: "Driver plumbing",
|
|
581
|
+
summary: "Start the steering-file language server",
|
|
582
|
+
details: `Start the language server for .gtd/ steering files on stdio.`,
|
|
779
583
|
},
|
|
780
584
|
]
|
|
781
585
|
|
|
782
586
|
const commandByToken = (token: string): CommandRow | undefined =>
|
|
783
587
|
COMMAND_ROWS.find((r) => r.token === token)
|
|
784
588
|
|
|
589
|
+
/** Every word that may follow `gtd`, for "did you mean". */
|
|
590
|
+
const COMMAND_WORDS: readonly string[] = [
|
|
591
|
+
...new Set([...COMMAND_ROWS.map((r) => r.token.split(" ")[0]!), "help", "version"]),
|
|
592
|
+
]
|
|
593
|
+
|
|
785
594
|
// ---------------------------------------------------------------------------
|
|
786
595
|
// Help rendering
|
|
787
596
|
// ---------------------------------------------------------------------------
|
|
788
597
|
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
598
|
+
const TOP_COLUMN = 28
|
|
599
|
+
const OPTION_COLUMN = 22
|
|
600
|
+
|
|
601
|
+
const synopsis = (row: CommandRow): string => {
|
|
602
|
+
const { token, arity } = row
|
|
603
|
+
if (arity === "none") return token
|
|
604
|
+
if ("names" in arity) return `${token} <${arity.names[0]}> <${arity.names[1]}>`
|
|
605
|
+
if ("variadic" in arity) return `${token} <${arity.name}> [${arity.variadic}...]`
|
|
606
|
+
if ("optional" in arity) return `${token} [${arity.name}]`
|
|
607
|
+
if ("many" in arity) return `${token} [${arity.name}...]`
|
|
608
|
+
return `${token} <${arity.name}>`
|
|
799
609
|
}
|
|
800
610
|
|
|
801
|
-
const START_SHORT_FORM = renderBlock("(no command) --workflow <name>", [
|
|
802
|
-
"Starts a new process authenticated as human, e.g.",
|
|
803
|
-
"'gtd --workflow <name>'",
|
|
804
|
-
])
|
|
805
|
-
|
|
806
|
-
const VERSION_BLOCK = renderBlock("version", ["Print version and exit"])
|
|
807
|
-
const HELP_BLOCK = renderBlock("help", ["Print this help and exit"])
|
|
808
|
-
|
|
809
611
|
const SPACE_FORM_FLAGS = new Set(["--workflow", "--var"])
|
|
810
612
|
|
|
811
613
|
const flagHeader = (row: FlagRow): string => {
|
|
812
614
|
if (row.valueHint === "") return row.name
|
|
615
|
+
if (row.arity === "optional") return `${row.name}[=${row.valueHint}]`
|
|
813
616
|
return SPACE_FORM_FLAGS.has(row.name)
|
|
814
617
|
? `${row.name} ${row.valueHint}`
|
|
815
618
|
: `${row.name}=${row.valueHint}`
|
|
816
619
|
}
|
|
817
620
|
|
|
621
|
+
const START_ROW = {
|
|
622
|
+
term: "--workflow <name>",
|
|
623
|
+
summary: "Start a process on a workflow; --var <name>=<value> pins one of its settings",
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const GLOBAL_OPTIONS: readonly (readonly [string, string])[] = [
|
|
627
|
+
["-v, --verbose", "Narrate on stderr what gtd resolved"],
|
|
628
|
+
["-h, --help", "Print help; gtd <command> --help for one command"],
|
|
629
|
+
["-V, --version", "Print the version"],
|
|
630
|
+
]
|
|
631
|
+
|
|
632
|
+
const EXTRA_TOPICS: readonly (readonly [string, string, string])[] = [
|
|
633
|
+
[
|
|
634
|
+
"help [command]",
|
|
635
|
+
"Print help for gtd or one command",
|
|
636
|
+
"Print gtd's overview, or with <command> that command's own help.",
|
|
637
|
+
],
|
|
638
|
+
["version", "Print the version", "Print gtd's version."],
|
|
639
|
+
]
|
|
640
|
+
|
|
641
|
+
const EXAMPLES = ` $ gtd --workflow <name> start a process
|
|
642
|
+
$ gtd next see what happens next
|
|
643
|
+
$ s=$(gtd land --json=script) && sh -c "$s" land the turn
|
|
644
|
+
$ gtd help land one command's details`
|
|
645
|
+
|
|
646
|
+
const lines = (block: readonly string[]): string => block.map((l) => `${l}\n`).join("")
|
|
647
|
+
|
|
648
|
+
const GROUPS: readonly Group[] = ["Core loop", "Start and stop", "Inspect", "Driver plumbing"]
|
|
649
|
+
|
|
818
650
|
export const renderHelp = (): string => {
|
|
819
|
-
const
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
? `${row.token} [${row.arity.name}...]`
|
|
831
|
-
: `${row.token} <${row.arity.name}>`
|
|
832
|
-
return renderBlock(header, row.details)
|
|
651
|
+
const groups = GROUPS.map((group) => {
|
|
652
|
+
const rows = COMMAND_ROWS.filter((r) => r.group === group).flatMap((r) =>
|
|
653
|
+
definition(synopsis(r), r.summary, TOP_COLUMN),
|
|
654
|
+
)
|
|
655
|
+
const start =
|
|
656
|
+
group === "Start and stop" ? definition(START_ROW.term, START_ROW.summary, TOP_COLUMN) : []
|
|
657
|
+
const extra =
|
|
658
|
+
group === "Driver plumbing"
|
|
659
|
+
? EXTRA_TOPICS.flatMap(([term, summary]) => definition(term, summary, TOP_COLUMN))
|
|
660
|
+
: []
|
|
661
|
+
return `${group}:\n${lines([...start, ...rows, ...extra])}`
|
|
833
662
|
})
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
663
|
+
return (
|
|
664
|
+
"Usage: gtd <command> [options]\n\n" +
|
|
665
|
+
lines(
|
|
666
|
+
wrap(
|
|
667
|
+
"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.",
|
|
668
|
+
),
|
|
669
|
+
) +
|
|
670
|
+
"\n" +
|
|
671
|
+
groups.join("\n") +
|
|
672
|
+
"\nOptions:\n" +
|
|
673
|
+
lines(GLOBAL_OPTIONS.flatMap(([term, text]) => definition(term, text, TOP_COLUMN))) +
|
|
674
|
+
`\nExamples:\n${EXAMPLES}\n\n` +
|
|
675
|
+
lines(
|
|
676
|
+
wrap(
|
|
677
|
+
"Run `gtd help <command>` for a command's details and options, and `gtd install` to learn how to drive gtd.",
|
|
678
|
+
),
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** Shown on stderr above a bare `gtd`'s usage error — the overview stays one `gtd help` away. */
|
|
684
|
+
const SHORT_USAGE = `Usage: gtd <command> [options]
|
|
685
|
+
|
|
686
|
+
gtd --workflow <name> start a process
|
|
687
|
+
gtd next print what should happen next
|
|
688
|
+
gtd land record what the tree now shows
|
|
689
|
+
|
|
690
|
+
`
|
|
846
691
|
|
|
692
|
+
const renderCommandHelp = (row: CommandRow): string => {
|
|
693
|
+
const options = FLAGS.filter((f) => f.name !== "--verbose" && f.scope(row.kind)).flatMap((f) =>
|
|
694
|
+
definition(flagHeader(f), f.helpFor?.[row.kind] ?? f.help, OPTION_COLUMN),
|
|
695
|
+
)
|
|
696
|
+
const global = definition("-v, --verbose", "Narrate on stderr what gtd resolved", OPTION_COLUMN)
|
|
847
697
|
return (
|
|
848
|
-
`Usage: gtd
|
|
849
|
-
|
|
850
|
-
|
|
698
|
+
`Usage: gtd ${synopsis(row)}${options.length > 0 ? " [options]" : ""}\n\n` +
|
|
699
|
+
lines(wrap(row.details)) +
|
|
700
|
+
`\nOptions:\n${lines([...options, ...global])}`
|
|
851
701
|
)
|
|
852
702
|
}
|
|
853
703
|
|
|
704
|
+
/** `gtd help <topic…>`'s text: one command's help, every `history` subcommand's for `history`, or undefined for an unknown topic. */
|
|
705
|
+
const renderTopicHelp = (topic: readonly string[]): string | undefined => {
|
|
706
|
+
const two = commandByToken(topic.slice(0, 2).join(" "))
|
|
707
|
+
if (two !== undefined && topic.length >= 2) return renderCommandHelp(two)
|
|
708
|
+
const [first = ""] = topic
|
|
709
|
+
const one = commandByToken(first)
|
|
710
|
+
if (one !== undefined) return renderCommandHelp(one)
|
|
711
|
+
const extra = EXTRA_TOPICS.find(([term]) => term.split(" ")[0] === first)
|
|
712
|
+
if (extra !== undefined) return `Usage: gtd ${extra[0]}\n\n${lines(wrap(extra[2]))}`
|
|
713
|
+
const family = COMMAND_ROWS.filter((r) => r.token.startsWith(`${first} `))
|
|
714
|
+
if (first !== "" && family.length > 0) return family.map(renderCommandHelp).join("\n")
|
|
715
|
+
return undefined
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** A usage error's tail: the nearest candidate when `word` looks like a typo of one, and where the full list is. */
|
|
719
|
+
const didYouMean = (
|
|
720
|
+
word: string,
|
|
721
|
+
candidates: readonly string[],
|
|
722
|
+
prefix = "",
|
|
723
|
+
pointer = "`gtd help` for every command",
|
|
724
|
+
): string => {
|
|
725
|
+
const guess = closest(word, candidates)
|
|
726
|
+
return guess === undefined
|
|
727
|
+
? ` — run ${pointer}`
|
|
728
|
+
: ` — did you mean \`${prefix}${guess}\`? Run ${pointer}`
|
|
729
|
+
}
|
|
730
|
+
|
|
854
731
|
// ---------------------------------------------------------------------------
|
|
855
732
|
// Tokenizer
|
|
856
733
|
// ---------------------------------------------------------------------------
|
|
@@ -867,6 +744,17 @@ interface TokenizeOk {
|
|
|
867
744
|
readonly byFlag: ReadonlyMap<string, readonly string[]>
|
|
868
745
|
readonly present: ReadonlySet<string>
|
|
869
746
|
readonly jsonSeen: boolean
|
|
747
|
+
/** The positional right after a bare `--json` — most likely a path meant as `--json=<path>`. */
|
|
748
|
+
readonly afterBareJson?: string
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const unknownOption = (tok: string, jsonSeen: boolean): TokenizeError => {
|
|
752
|
+
const candidates = tok.startsWith("--") ? FLAGS.map((f) => f.name) : []
|
|
753
|
+
return {
|
|
754
|
+
kind: "error",
|
|
755
|
+
message: `gtd: unknown option '${tok}'${didYouMean(tok.split("=")[0]!, candidates, "", "`gtd help <command>` for its options")}`,
|
|
756
|
+
jsonSeen,
|
|
757
|
+
}
|
|
870
758
|
}
|
|
871
759
|
|
|
872
760
|
// fallow-ignore-next-line complexity
|
|
@@ -875,35 +763,25 @@ const tokenize = (tail: readonly string[]): TokenizeError | TokenizeOk => {
|
|
|
875
763
|
const byFlag = new Map<string, string[]>()
|
|
876
764
|
const present = new Set<string>()
|
|
877
765
|
let jsonSeen = false
|
|
766
|
+
let afterBareJson: string | undefined
|
|
878
767
|
|
|
879
768
|
for (let i = 0; i < tail.length; i++) {
|
|
880
769
|
const original = tail[i]!
|
|
881
770
|
const tok = SINGLE_DASH_ALIASES.get(original) ?? original
|
|
882
|
-
if (!tok.startsWith("
|
|
771
|
+
if (!tok.startsWith("-") || tok === "-") {
|
|
772
|
+
if (tail[i - 1] === "--json") afterBareJson ??= original
|
|
883
773
|
positionals.push(original)
|
|
884
774
|
continue
|
|
885
775
|
}
|
|
886
|
-
const row = flagByToken(tok)
|
|
887
|
-
if (row === undefined)
|
|
888
|
-
return {
|
|
889
|
-
kind: "error",
|
|
890
|
-
message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
|
|
891
|
-
jsonSeen,
|
|
892
|
-
}
|
|
893
|
-
}
|
|
776
|
+
const row = tok.startsWith("--") ? flagByToken(tok) : undefined
|
|
777
|
+
if (row === undefined) return unknownOption(tok, jsonSeen)
|
|
894
778
|
const eq = tok.indexOf("=")
|
|
895
779
|
const alreadyPresent = present.has(row.name)
|
|
896
780
|
present.add(row.name)
|
|
897
781
|
if (row.name === "--json") jsonSeen = true
|
|
898
782
|
|
|
899
783
|
if (row.arity === 0) {
|
|
900
|
-
if (eq !== -1)
|
|
901
|
-
return {
|
|
902
|
-
kind: "error",
|
|
903
|
-
message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
|
|
904
|
-
jsonSeen,
|
|
905
|
-
}
|
|
906
|
-
}
|
|
784
|
+
if (eq !== -1) return unknownOption(tok, jsonSeen)
|
|
907
785
|
if (!byFlag.has(row.name)) byFlag.set(row.name, [])
|
|
908
786
|
continue
|
|
909
787
|
}
|
|
@@ -961,7 +839,14 @@ const tokenize = (tail: readonly string[]): TokenizeError | TokenizeOk => {
|
|
|
961
839
|
byFlag.set(row.name, [...existing, raw])
|
|
962
840
|
}
|
|
963
841
|
|
|
964
|
-
return {
|
|
842
|
+
return {
|
|
843
|
+
kind: "ok",
|
|
844
|
+
positionals,
|
|
845
|
+
byFlag,
|
|
846
|
+
present,
|
|
847
|
+
jsonSeen,
|
|
848
|
+
...(afterBareJson !== undefined ? { afterBareJson } : {}),
|
|
849
|
+
}
|
|
965
850
|
}
|
|
966
851
|
|
|
967
852
|
// ---------------------------------------------------------------------------
|
|
@@ -1056,24 +941,43 @@ const buildLandCommand = (bag: {
|
|
|
1056
941
|
...(bag["--model"] !== undefined ? { model: bag["--model"] } : {}),
|
|
1057
942
|
})
|
|
1058
943
|
|
|
944
|
+
const HELP_FLAGS = new Set(["--help", "-h"])
|
|
945
|
+
|
|
946
|
+
/** `--help`/`-h` anywhere: the named command's own help, else the overview — never a usage error, so a typo'd command still gets help. */
|
|
947
|
+
const helpPlan = (argv: readonly string[]): CliPlan => {
|
|
948
|
+
const tokenized = tokenize(argv.slice(2).filter((tok) => !HELP_FLAGS.has(tok)))
|
|
949
|
+
const topic = tokenized.kind === "ok" ? tokenized.positionals : []
|
|
950
|
+
return { kind: "output", stdout: renderTopicHelp(topic) ?? renderHelp() }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** `gtd help [topic…]`: an unknown topic is a usage error naming the nearest command. */
|
|
954
|
+
const helpCommandPlan = (topic: readonly string[], json: boolean): CliPlan => {
|
|
955
|
+
if (topic.length === 0) return { kind: "output", stdout: renderHelp() }
|
|
956
|
+
const text = renderTopicHelp(topic)
|
|
957
|
+
if (text !== undefined) return { kind: "output", stdout: text }
|
|
958
|
+
return usagePlan(
|
|
959
|
+
`gtd: help: no command '${topic.join(" ")}'${didYouMean(topic[0]!, COMMAND_WORDS, "gtd help ")}`,
|
|
960
|
+
json,
|
|
961
|
+
)
|
|
962
|
+
}
|
|
963
|
+
|
|
1059
964
|
// fallow-ignore-next-line complexity
|
|
1060
965
|
export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
1061
|
-
if (argv.
|
|
1062
|
-
return { kind: "output", stdout: renderHelp() }
|
|
966
|
+
if (argv.some((tok) => HELP_FLAGS.has(tok))) return helpPlan(argv)
|
|
1063
967
|
if (argv.includes("--version") || argv.includes("-V")) {
|
|
1064
968
|
return { kind: "output", stdout: `${GTD_VERSION}\n` }
|
|
1065
969
|
}
|
|
1066
970
|
|
|
1067
971
|
const tokenized = tokenize(argv.slice(2))
|
|
1068
972
|
if (tokenized.kind === "error") return usagePlan(tokenized.message, tokenized.jsonSeen)
|
|
1069
|
-
const { positionals, byFlag, present, jsonSeen } = tokenized
|
|
973
|
+
const { positionals, byFlag, present, jsonSeen, afterBareJson } = tokenized
|
|
1070
974
|
|
|
1071
975
|
const first = positionals[0]
|
|
1072
976
|
const workflowRaw = byFlag.get("--workflow")?.[0]
|
|
1073
977
|
const workflowPresent = workflowRaw !== undefined
|
|
1074
978
|
|
|
1075
979
|
if (first === "version") return { kind: "output", stdout: `${GTD_VERSION}\n` }
|
|
1076
|
-
if (first === "help") return
|
|
980
|
+
if (first === "help") return helpCommandPlan(positionals.slice(1), jsonSeen)
|
|
1077
981
|
|
|
1078
982
|
const isHistory = first === "history"
|
|
1079
983
|
const row =
|
|
@@ -1082,8 +986,14 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1082
986
|
: commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first)
|
|
1083
987
|
|
|
1084
988
|
if (isHistory && row === undefined) {
|
|
989
|
+
const subcommands = COMMAND_ROWS.filter((r) => r.token.startsWith("history ")).map(
|
|
990
|
+
(r) => r.token.split(" ")[1]!,
|
|
991
|
+
)
|
|
992
|
+
const typo = closest(positionals[1] ?? "", subcommands)
|
|
1085
993
|
return usagePlan(
|
|
1086
|
-
|
|
994
|
+
typo !== undefined
|
|
995
|
+
? `gtd history: unknown subcommand '${positionals[1]}' — did you mean \`gtd history ${typo}\`?`
|
|
996
|
+
: "gtd history: expected a subcommand — list, show <ref>, metrics [ref...], judges [ref...] or fetch [remote]",
|
|
1087
997
|
jsonSeen,
|
|
1088
998
|
)
|
|
1089
999
|
}
|
|
@@ -1092,7 +1002,7 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1092
1002
|
// "answer" arriving as `positionals[1]`, a second token after "judge" —
|
|
1093
1003
|
// detected below via `judgeAnswer`, not through a second dispatchable
|
|
1094
1004
|
// `COMMAND_ROWS` row: the `"judge answer"`-tokened row above exists for
|
|
1095
|
-
//
|
|
1005
|
+
// its help. It CAN also be matched by
|
|
1096
1006
|
// `commandByToken` directly, but only if a caller passes the single
|
|
1097
1007
|
// (space-containing) argv token `"judge answer"` — an unusual shell quoting
|
|
1098
1008
|
// choice, not the normal two-argv-token invocation `gtd judge answer` — and
|
|
@@ -1126,16 +1036,15 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1126
1036
|
if (violation !== undefined) return usagePlan(violation, jsonSeen)
|
|
1127
1037
|
if (first === undefined) {
|
|
1128
1038
|
return usagePlan(
|
|
1129
|
-
"gtd: missing command — gtd
|
|
1130
|
-
"run `gtd install`, or copy one from " +
|
|
1131
|
-
"https://github.com/pmelab/gtd/blob/main/docs/driver.md's " +
|
|
1132
|
-
'"A complete minimal driver" section and run that, or see usage ' +
|
|
1133
|
-
"above (`gtd --help`)",
|
|
1039
|
+
"gtd: missing command — run `gtd help` for every command, or `gtd install` to learn how to drive gtd",
|
|
1134
1040
|
jsonSeen,
|
|
1135
|
-
|
|
1041
|
+
SHORT_USAGE,
|
|
1136
1042
|
)
|
|
1137
1043
|
}
|
|
1138
|
-
return usagePlan(
|
|
1044
|
+
return usagePlan(
|
|
1045
|
+
`gtd: unknown command '${first}'${didYouMean(first, COMMAND_WORDS, "gtd ")}`,
|
|
1046
|
+
jsonSeen,
|
|
1047
|
+
)
|
|
1139
1048
|
}
|
|
1140
1049
|
|
|
1141
1050
|
// From here, `kind` is a genuine `Command["kind"]`. `judgeAnswer` consumes
|
|
@@ -1155,12 +1064,20 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1155
1064
|
} else {
|
|
1156
1065
|
// `kind === "start"` is unreachable here: `selectsStart` only fires when
|
|
1157
1066
|
// `first === undefined`, which the `if` branch above already handled.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1067
|
+
// A bare `gtd door` lists the doors — which only the loaded config knows.
|
|
1068
|
+
const bare =
|
|
1069
|
+
(kind === "check" && restPositionals.length === 0 && present.has("--open-threads")) ||
|
|
1070
|
+
(kind === "door" && restPositionals.length === 0)
|
|
1071
|
+
const arityMsg = bare
|
|
1161
1072
|
? undefined
|
|
1162
1073
|
: arityError(isHistory ? row!.token : first!, restPositionals, row!.arity)
|
|
1163
|
-
if (arityMsg !== undefined)
|
|
1074
|
+
if (arityMsg !== undefined) {
|
|
1075
|
+
const hint =
|
|
1076
|
+
afterBareJson !== undefined && restPositionals.includes(afterBareJson)
|
|
1077
|
+
? ` — to print one field, write --json=${afterBareJson}`
|
|
1078
|
+
: ""
|
|
1079
|
+
return usagePlan(arityMsg + hint, jsonSeen)
|
|
1080
|
+
}
|
|
1164
1081
|
}
|
|
1165
1082
|
|
|
1166
1083
|
const violation = scopeViolation(kind, present)
|
|
@@ -1226,7 +1143,11 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1226
1143
|
if (kind === "door") {
|
|
1227
1144
|
return {
|
|
1228
1145
|
kind: "command",
|
|
1229
|
-
command: {
|
|
1146
|
+
command: {
|
|
1147
|
+
kind: "door",
|
|
1148
|
+
...(restPositionals[0] !== undefined ? { name: restPositionals[0] } : {}),
|
|
1149
|
+
args: restPositionals.slice(1),
|
|
1150
|
+
},
|
|
1230
1151
|
json,
|
|
1231
1152
|
verbose,
|
|
1232
1153
|
}
|
|
@@ -1541,7 +1462,7 @@ export const runCli = (argv: readonly string[], io: CliIo): Effect.Effect<void,
|
|
|
1541
1462
|
|
|
1542
1463
|
if (plan.kind === "usage") {
|
|
1543
1464
|
return Effect.sync(() => {
|
|
1544
|
-
// The
|
|
1465
|
+
// The short usage (missing-command only) and the `--json`
|
|
1545
1466
|
// envelope both move to stderr — stdout stays byte-empty on a usage
|
|
1546
1467
|
// error exactly like every other failure surface.
|
|
1547
1468
|
if (plan.stdout.length > 0) io.stderr(plan.stdout)
|