@pmelab/gtd 9.6.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +508 -311
- package/dist/gtd.bundle.mjs +2494 -1919
- package/package.json +3 -2
- package/schema.json +6 -6
package/README.md
CHANGED
|
@@ -90,28 +90,28 @@ The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
|
|
|
90
90
|
opening beat, which the human triggered by re-running the driver: land that one,
|
|
91
91
|
since changing nothing is itself a declared outcome at some gates); `"capture"`
|
|
92
92
|
means a human gate the human already acted on (land it immediately); `"script"`
|
|
93
|
-
means the driver runs `
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pattern on it.
|
|
93
|
+
means the driver runs `gtd next`'s own plain-text output itself, then lands it;
|
|
94
|
+
`"prompt"` means feed `gtd next`'s output to your agent — using the accompanying
|
|
95
|
+
`session.id`/`session.resume` fields off `gtd next --json` to continue or start
|
|
96
|
+
that agent conversation (see "Driving the loop" below) — then run `gtd land`
|
|
97
|
+
once it's done. gtd itself never executes anything — the driver owns running
|
|
98
|
+
scripts. Every `gtd next` call is strictly mutation-free, safe to poll or peek
|
|
99
|
+
at any time: `session.id`/`session.resume` are DERIVED, never stored, so looking
|
|
100
|
+
is free — nothing distinguishes a peek from a dispatch, and there is no separate
|
|
101
|
+
claiming form at all. A `prompt` beat whose turn changes nothing still lands:
|
|
102
|
+
`gtd land` commits an EMPTY `gtd(<actor>): <state>` attempt instead of silently
|
|
103
|
+
doing nothing, so the fruitless dispatch is visible in history rather than
|
|
104
|
+
invisible. `"kind": "stalled"` is derived from that history — HEAD is an empty
|
|
105
|
+
attempt at the resting state and the tree is clean — so every `gtd next --json`
|
|
106
|
+
call reports it, and it stays reported on a repeat (there's no marker to
|
|
107
|
+
consume) until something actually changes. The fix for a repeated stall is
|
|
108
|
+
either a better prompt, a `retry:` cap on the state that redirects to an
|
|
109
|
+
escalation state after N fruitless attempts, or — if the state can legitimately
|
|
110
|
+
finish with nothing to change — declaring a `C` (clean-tree) pattern on it.
|
|
111
111
|
|
|
112
112
|
An `on` edge may also carry a short imperative `action` (e.g. `Accept plan`)
|
|
113
113
|
alongside its existing `describe` sentence — a human-facing name for the choice
|
|
114
|
-
itself, not just the underlying glob pattern. `gtd
|
|
114
|
+
itself, not just the underlying glob pattern. `gtd next` (plain and `--json`)
|
|
115
115
|
and `gtd visualize`'s diagram and inspector all render the two composed
|
|
116
116
|
together: the `action` leads when the edge declares one, with the raw pattern
|
|
117
117
|
(and `describe`) still shown alongside it, so a human choosing between routes
|
|
@@ -121,14 +121,14 @@ reads intent first, glob second.
|
|
|
121
121
|
to the tree — a hand-edit to real code, a scratch note in some file, anything at
|
|
122
122
|
all — as a sketch to be finished, never as work to be preserved: there is no
|
|
123
123
|
fork on which steering file you happen to create, and `idle` has exactly one
|
|
124
|
-
outgoing edge, into `unwind`. `.gtd/TODO.md`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
outgoing edge, into `unwind`. `.gtd/TODO.md` is a good default place to start
|
|
125
|
+
sketching when you'd rather write a note than touch code — despite living beside
|
|
126
|
+
the workflow's own state files, that sketch is your input for `design.triage` to
|
|
127
|
+
fold in, never gtd bookkeeping to preserve or ignore. `unwind` reverts that
|
|
128
|
+
whole diff back out of your working tree (`git revert --no-commit` against the
|
|
129
|
+
commit it just landed in) before planning ever starts — your tree is back to
|
|
130
|
+
exactly what it was, and your intent doesn't disappear, it survives in history
|
|
131
|
+
for `design.triage` to read.
|
|
132
132
|
|
|
133
133
|
From there, `start-gate.check` runs your test suite on the tree as it now stands
|
|
134
134
|
— which, thanks to the unwind, is simply your repo's own baseline. The rule is
|
|
@@ -222,14 +222,8 @@ rather than sitting beside it) — an actionable round leaves that identity
|
|
|
222
222
|
entirely, so it starts a fresh session like any other process. A comment is what
|
|
223
223
|
asks for changes; landing with no comment signs off, whatever the boxes say,
|
|
224
224
|
which collapses the whole process into one commit (a **squash finale** whose
|
|
225
|
-
message an agent drafts). Deleting `.gtd/REVIEW.md` is refused
|
|
226
|
-
|
|
227
|
-
the repo root) changes nothing about that refusal, since it only asks whether
|
|
228
|
-
the step's diff deletes the file. The exact-path exemption issue #128 fixed
|
|
229
|
-
still matters elsewhere, though: the `deciding` state's own classification and
|
|
230
|
-
the feedback-progress guard both exclude the state's own `file:` by exact path,
|
|
231
|
-
not merely a `.gtd/` prefix, so your own edit to a relocated steering file is
|
|
232
|
-
never mistaken for a code comment.
|
|
225
|
+
message an agent drafts). Deleting `.gtd/REVIEW.md` is refused — the guard only
|
|
226
|
+
asks whether the step's diff deletes the file.
|
|
233
227
|
|
|
234
228
|
A second entry, the same review tail's own direct entry point —
|
|
235
229
|
`gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
|
|
@@ -262,27 +256,9 @@ to target a state.
|
|
|
262
256
|
Every agent state routes its model through two `vars` tiers — `plannerModel`
|
|
263
257
|
(heavier planning and review) and `coderModel` (the coding turns) — so you can
|
|
264
258
|
repoint the models globally in one place (a `vars:` edit or a `GTD_PLANNERMODEL`
|
|
265
|
-
override) instead of per state.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
machine, not just the templates that read/write the file. `todoFile` (default
|
|
269
|
-
`.gtd/TODO.md`, `idle`'s own `file:` hint) is the one exception: it's the only
|
|
270
|
-
per-file var gtd never writes and never reads, since it names your own sketch
|
|
271
|
-
pad, not a file any state produces. A separate `stateDir` var (default `.gtd`)
|
|
272
|
-
names only where the check scripts keep their own scratch/bookkeeping,
|
|
273
|
-
independent of the per-file vars — relocate one steering file without touching
|
|
274
|
-
it. The workflow declares its plumbing directory once, at the top level of the
|
|
275
|
-
`workflow:` value (`stateDir: <%= it.vars.stateDir %>` in the bundled template),
|
|
276
|
-
rendered from that same `stateDir` var — this is what the engine itself reads to
|
|
277
|
-
tell gtd's own bookkeeping apart from your code (e.g. the review window's revert
|
|
278
|
-
pathspec, the step guards' code-vs-plumbing check), not just a value the
|
|
279
|
-
templates happen to share. The rendered value is constrained: it must name a
|
|
280
|
-
directory inside the repository — not the repo root, not an absolute path, and
|
|
281
|
-
not a path that escapes the repository via `..` — and it must be spelled
|
|
282
|
-
canonically: no `.` and no empty path segments (`a/./state` or `a//state` are
|
|
283
|
-
rejected, not rewritten), though a leading `./` and a trailing `/` are accepted
|
|
284
|
-
— or the next `gtd next`/`gtd status`/`gtd land` fails loudly with that message,
|
|
285
|
-
rather than silently excluding the whole tree from the review window's revert.
|
|
259
|
+
override) instead of per state. Every steering file the bundled workflow reads
|
|
260
|
+
or writes lives at a fixed path under `.gtd/`; `testCommand`, `plannerModel`,
|
|
261
|
+
`coderModel`, and `reviewBase` are the only vars left to tune.
|
|
286
262
|
|
|
287
263
|
To inspect or change the machine itself, see [Configuration](#configuration) —
|
|
288
264
|
the workflow is just `.gtdrc` config.
|
|
@@ -301,18 +277,19 @@ Commands:
|
|
|
301
277
|
Takes no argument. Run once per repo; refuses if a gtd
|
|
302
278
|
config already exists. Leaves the file uncommitted for you
|
|
303
279
|
to review and commit
|
|
304
|
-
land Land whatever the tree now shows at the currently
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
280
|
+
land Land whatever the tree now shows at the currently resolved
|
|
281
|
+
rest — a human capture, an agent/check turn, an empty
|
|
282
|
+
attempt (a fruitless prompt turn), or a squash. Pass
|
|
283
|
+
--cost=<n> (optionally --model=<name>) to record the
|
|
284
|
+
just-finished invocation's token cost and model on the
|
|
285
|
+
turn commit (summed into it.processCost/
|
|
286
|
+
processCostByModel). Plain (the default) prints ONLY the
|
|
287
|
+
script that records the landing; a driver runs it, e.g.
|
|
288
|
+
`gtd land | sh`. --json/--sh instead emit script (that
|
|
289
|
+
same script, byte-identical) alongside settled, idle,
|
|
290
|
+
state (the post-land target), subject, cost and model —
|
|
291
|
+
--json/--sh are mutually exclusive. Exits 0 on success, 1
|
|
292
|
+
on any refusal — see the Exit codes section below
|
|
316
293
|
(no command) --entry <state>
|
|
317
294
|
Starts a new process authenticated as human, e.g.
|
|
318
295
|
'gtd --entry <state>'
|
|
@@ -327,29 +304,35 @@ Commands:
|
|
|
327
304
|
Refuses on a dirty working tree, when there is no retained
|
|
328
305
|
history, or when HEAD has advanced past the squash with
|
|
329
306
|
commits that would be lost
|
|
330
|
-
next Print the resolved rest's
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
307
|
+
next Print the resolved rest's beat (no mutation, safe to
|
|
308
|
+
poll), in one of three encodings. Plain (the default): a
|
|
309
|
+
status summary, a blank line, then the step verbatim —
|
|
310
|
+
except at a prompt rest, which is the bare step (plus the
|
|
311
|
+
self-validation instruction when applicable) with no
|
|
312
|
+
header, since those bytes are the agent's own input. --json
|
|
313
|
+
emits the one structured surface gtd has: kind
|
|
314
|
+
(capture|message|script|prompt|stalled) selects what a
|
|
315
|
+
driver does, content is what it runs or shows, idle marks
|
|
316
|
+
the workflow's initial state with a clean tree, plus the
|
|
317
|
+
prompt session, model, validate script, log path, changes,
|
|
318
|
+
next and the resting state's own fields. --sh emits the
|
|
319
|
+
same fields as gtd_-prefixed POSIX shell assignments.
|
|
320
|
+
--json/--sh are mutually exclusive. Exits 0 — see the
|
|
321
|
+
Exit codes section below
|
|
338
322
|
validate Print the script that formats (when declared) then
|
|
339
323
|
validates the resolved rest's steering file, using its
|
|
340
324
|
mode's commands (its file:/mode:), instead of running it —
|
|
341
325
|
a driver runs the script and reads the findings from its
|
|
342
|
-
own exit code/output. Always exits 0;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
(instruction + findings) and exits with the
|
|
348
|
-
validator's own code
|
|
326
|
+
own exit code/output. Always exits 0; prints "nothing to
|
|
327
|
+
validate" when the resolved rest declares nothing to run.
|
|
328
|
+
On a non-zero validate exit the emitted script prints a
|
|
329
|
+
ready-to-send fix prompt (instruction + findings) and
|
|
330
|
+
exits with the validator's own code
|
|
349
331
|
lsp Start the LSP server for .gtd/ steering files (stdio)
|
|
350
332
|
visualize Serve an interactive diagram of the active workflow on a
|
|
351
|
-
local web server (--port <n>, --no-open
|
|
352
|
-
|
|
333
|
+
local web server (--port <n>, --no-open). Prints the
|
|
334
|
+
chosen port on its own line — with --port 0, this is the
|
|
335
|
+
only way to learn which port was picked
|
|
353
336
|
check <mode> <file>
|
|
354
337
|
Read <file> and run the built-in steering format named
|
|
355
338
|
<mode> (see `gtd validate`'s modes: qa, review) over its
|
|
@@ -370,7 +353,11 @@ Commands:
|
|
|
370
353
|
help Print this help and exit
|
|
371
354
|
|
|
372
355
|
Options:
|
|
373
|
-
--json
|
|
356
|
+
--json (gtd next/gtd land only) output structured JSON
|
|
357
|
+
instead of plain text. Mutually exclusive with --sh
|
|
358
|
+
--sh (gtd next/gtd land only) output gtd_-prefixed POSIX
|
|
359
|
+
shell assignments instead of plain text. Mutually
|
|
360
|
+
exclusive with --json
|
|
374
361
|
--port=<n> (gtd visualize only) port to serve on (default: a free port)
|
|
375
362
|
--no-open (gtd visualize only) do not open the browser
|
|
376
363
|
--cost=<n> (gtd land only) record the invocation's token cost
|
|
@@ -386,23 +373,73 @@ Options:
|
|
|
386
373
|
instead run the qa open-questions predicate over <file>,
|
|
387
374
|
printing each unanswered question one per line and exiting
|
|
388
375
|
non-zero when any remain
|
|
389
|
-
--
|
|
376
|
+
--verbose enable stderr narration for this invocation: which rest
|
|
377
|
+
resolved, which declared pattern each pending change
|
|
378
|
+
matched, which review-window action was emitted, and how
|
|
379
|
+
config resolved across layers. Aliased to -v
|
|
380
|
+
--version, -V Print version and exit
|
|
390
381
|
--help, -h Print this help and exit
|
|
391
382
|
```
|
|
392
383
|
|
|
393
|
-
|
|
384
|
+
### Plain output is not a parsing surface
|
|
385
|
+
|
|
386
|
+
`gtd next`'s plain encoding is for a human, or a driver that merely displays it
|
|
387
|
+
— never for scraping. Parsing lives in `--json`/`--sh`, both of which read from
|
|
388
|
+
the exact same field set (`gtd install`'s briefing has the full reference);
|
|
389
|
+
anything that greps, cuts, or `awk`s plain text is unsupported, and its shape
|
|
390
|
+
may change across releases with no warning.
|
|
391
|
+
|
|
392
|
+
### Exit codes
|
|
393
|
+
|
|
394
|
+
Closed at five numbers, five meanings — a new command never grows this table;
|
|
395
|
+
whose turn is next lives in `gtd next --json`'s own `kind` field instead.
|
|
396
|
+
|
|
397
|
+
| Code | Meaning |
|
|
398
|
+
| --------- | ---------------- |
|
|
399
|
+
| 0 | success |
|
|
400
|
+
| 1 | runtime error |
|
|
401
|
+
| 2 | usage error |
|
|
402
|
+
| 130 / 143 | SIGINT / SIGTERM |
|
|
403
|
+
|
|
404
|
+
Every command follows this table uniformly, `next`/`land` included: `0` on
|
|
405
|
+
success, `1` on refusal, `2` on usage error, `130`/`143` when gtd itself dies by
|
|
406
|
+
that signal — a parent's `wait` sees a real signal death (`WIFSIGNALED`), not a
|
|
407
|
+
chosen exit code that merely reuses the same number. No command's exit code
|
|
408
|
+
carries a second meaning.
|
|
409
|
+
|
|
410
|
+
**Migration — read this even if you already migrated for a prior release.** This
|
|
411
|
+
is the second inversion in as many releases, folded into one note rather than
|
|
412
|
+
two to compose in your head: `10`/`20` (whose turn was next) are gone — every
|
|
413
|
+
command exits `0`/`1`/`2` uniformly now; `gtd status` is gone (folded into
|
|
414
|
+
`gtd next`); and plain `gtd next` now prints a status header at every kind
|
|
415
|
+
EXCEPT `prompt` — agent input is untouched, since plain `gtd next` at a `prompt`
|
|
416
|
+
rest is byte-identical to before. A driver must read whose turn is next off
|
|
417
|
+
`gtd next --json`'s own `kind` field
|
|
418
|
+
(`capture`/`message`/`script`/`prompt`/`stalled`) — never off gtd's exit code,
|
|
419
|
+
which no longer carries that signal at all.
|
|
420
|
+
|
|
421
|
+
Every usage mistake — an unknown option or command, missing/extra arguments, a
|
|
422
|
+
scope violation (e.g. `--cost` on a command other than `gtd land`), a bad flag
|
|
423
|
+
value — is a USAGE error (`2`), never a runtime error (`1`): nothing was even
|
|
424
|
+
attempted. `--help`/`--version` still exit `0`.
|
|
425
|
+
|
|
426
|
+
`--version` (`-V`) / `gtd version` and `--help` (`-h`) / `gtd help`
|
|
394
427
|
short-circuit before any git or repository-state work — they run outside a repo
|
|
395
|
-
and in any repo state
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
`
|
|
405
|
-
|
|
428
|
+
and in any repo state, and print to **stdout** at exit 0. `--verbose` (`-v`) is
|
|
429
|
+
not a short-circuit — it gates narration for whatever command follows (see
|
|
430
|
+
[Narration and remediation](#narration-and-remediation) below). Bare `gtd` (no
|
|
431
|
+
subcommand) is a usage error that exits 2 — gtd decides and prints, full stop;
|
|
432
|
+
driving a loop is a driver's job, not a bundled command (see
|
|
433
|
+
[Driving the loop](#driving-the-loop) below) — printing its help text to
|
|
434
|
+
**stderr**, not stdout: stdout stays byte-empty on every failure, a usage error
|
|
435
|
+
included (see [Error envelope](#error-envelope) below). Any other, truly unknown
|
|
436
|
+
subcommand is likewise a usage error exiting 2 without touching the repository.
|
|
437
|
+
The state commands (`land`, `--entry`, `abandon`, `restore`, `next`, `status`,
|
|
438
|
+
`validate`) must run from the **repository root** — gtd derives the workflow,
|
|
439
|
+
pending changes, and process history relative to cwd, so they refuse with a
|
|
440
|
+
clear error from a subdirectory; `lsp`, `init`, `visualize`, `check`, and
|
|
441
|
+
`install` are standalone and run from anywhere (see each command's own help
|
|
442
|
+
entry).
|
|
406
443
|
|
|
407
444
|
`--json`, `--cost=<n>`, `--model=<name>` (the latter two only for `gtd land`),
|
|
408
445
|
`--entry <state>` (no other command at all), and `--var <name>=<value>` (with
|
|
@@ -418,7 +455,7 @@ command are all usage errors too — landing and entering are different verbs, s
|
|
|
418
455
|
`--model`, `--model` without `--cost`, or `--cost`/`--model` on any command
|
|
419
456
|
other than `gtd land` are all usage errors.
|
|
420
457
|
|
|
421
|
-
### `gtd
|
|
458
|
+
### `gtd next`'s `Next:`/`next`
|
|
422
459
|
|
|
423
460
|
Both plain and `--json` output include a headline preview of what would happen
|
|
424
461
|
next: the first declared `on` edge whose pattern matches the pending changes AS
|
|
@@ -435,23 +472,68 @@ real `gtd land` lands.
|
|
|
435
472
|
|
|
436
473
|
### Error envelope
|
|
437
474
|
|
|
438
|
-
|
|
439
|
-
|
|
475
|
+
**stdout is either the complete artifact or byte-empty — never a partial write
|
|
476
|
+
followed by an error.** Every command buffers everything it would print and
|
|
477
|
+
flushes that buffer to stdout exactly once, only after it succeeds; on any
|
|
478
|
+
failure the buffer is simply discarded, so stdout never carries a half-written
|
|
479
|
+
prompt/script alongside a message about why it stopped. `gtd visualize` is the
|
|
480
|
+
one exception worth knowing: it flushes its served-URL line immediately, before
|
|
481
|
+
blocking on `Ctrl-C`, since a flush-on-success would never otherwise fire.
|
|
482
|
+
|
|
483
|
+
Any invocation that carries `--json` (valid only for `gtd next`/`gtd land` —
|
|
484
|
+
every other command usage-errors on it) reports a failure as a machine-readable
|
|
485
|
+
envelope on **stderr** — including the scope violation itself, so a driver that
|
|
486
|
+
always adds `--json` still gets a parseable envelope on every failure, not only
|
|
487
|
+
`gtd next`'s/`gtd land`'s own:
|
|
440
488
|
|
|
441
489
|
```json
|
|
442
490
|
{ "state": "error", "prompt": "<message>" }
|
|
443
491
|
```
|
|
444
492
|
|
|
445
|
-
This covers every failure mode, not just a command's own refusal: a
|
|
446
|
-
error** (an unknown flag, a missing argument, `gtd --entry version`'s
|
|
447
|
-
enterable state") and a **defect** (a layer throwing outside
|
|
448
|
-
channel) both get the same envelope — there is
|
|
449
|
-
`--json` without one
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
493
|
+
This covers every failure mode, not just a command's own refusal (exit 1): a
|
|
494
|
+
**usage error** (an unknown flag, a missing argument, `gtd --entry version`'s
|
|
495
|
+
"not an enterable state" — exit 2) and a **defect** (a layer throwing outside
|
|
496
|
+
the ordinary error channel — exit 1) both get the same envelope shape — there is
|
|
497
|
+
no failure path that reaches `--json` without one — but a usage error's exit
|
|
498
|
+
code is 2, never 1, so a driver can tell "you invoked gtd wrong" apart from "gtd
|
|
499
|
+
refused/broke" (see [Exit codes](#exit-codes)).
|
|
500
|
+
|
|
501
|
+
A human-readable `gtd: <message>` line is also always written to **stderr**,
|
|
502
|
+
right after the envelope — stdout carries neither one on a failing run. Stderr
|
|
503
|
+
always carries exactly one `gtd: ` prefix: a message already authored with its
|
|
504
|
+
own `gtd:`/`gtd <cmd>:` prefix is never doubled.
|
|
505
|
+
|
|
506
|
+
**Accepted cost:** a driver that pipes stdout into `jq` on a failed run now
|
|
507
|
+
reads nothing instead of a parseable error object — it must read stderr or the
|
|
508
|
+
exit code to learn why a run failed.
|
|
509
|
+
|
|
510
|
+
### Narration and remediation
|
|
511
|
+
|
|
512
|
+
Stderr carries two things beyond the `gtd: ` message line above: NARRATION,
|
|
513
|
+
gated by `--verbose`/`-v`, and REMEDIATION, unconditional.
|
|
514
|
+
|
|
515
|
+
`--verbose` (alias `-v`) turns on one line of commentary per in-process fact a
|
|
516
|
+
command's dispatch already computes — which rest resolved, which declared
|
|
517
|
+
pattern each pending change matched, which review-window action was emitted, and
|
|
518
|
+
how config resolved across `.gtdrc` layers. Without it, none of this is printed;
|
|
519
|
+
stdout is never touched either way — narration is a stderr-only concern, exactly
|
|
520
|
+
like the error envelope above.
|
|
521
|
+
|
|
522
|
+
A failure's remediation detail is unconditional — it prints at every verbosity,
|
|
523
|
+
on the line(s) right after the `gtd: `-prefixed message, each indented two
|
|
524
|
+
spaces: a bad config key names the offending key and which `.gtdrc` layer it
|
|
525
|
+
came from, a corrupted ref names the ref, and a missing binary in a steering
|
|
526
|
+
mode's `format:`/`validate:` command names the resolved `$PATH` it was looked up
|
|
527
|
+
in.
|
|
528
|
+
|
|
529
|
+
### Non-interactive today
|
|
530
|
+
|
|
531
|
+
gtd is non-interactive: no readline, no `/dev/tty`, no prompt call anywhere in
|
|
532
|
+
`src/`. If interaction is ever added, it goes to `/dev/tty`, never stderr, and
|
|
533
|
+
never blocks — a question fails with a code when the tty cannot be opened rather
|
|
534
|
+
than hanging. The reason is the section above: stderr already has two occupants,
|
|
535
|
+
narration and remediation, and a question mixed into that stream would deadlock
|
|
536
|
+
a driver that never reads stderr.
|
|
455
537
|
|
|
456
538
|
## Driving the loop
|
|
457
539
|
|
|
@@ -485,38 +567,62 @@ not of the loop itself; see
|
|
|
485
567
|
below for a worked example.
|
|
486
568
|
|
|
487
569
|
A workflow state can declare an optional `label:` — a human-readable display
|
|
488
|
-
name surfaced in `gtd next --json
|
|
570
|
+
name surfaced in `gtd next --json`/plain `gtd next`. The driver uses it for its
|
|
489
571
|
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
490
572
|
can use it the same way.
|
|
491
573
|
|
|
574
|
+
A machine may also declare `system:` — like `model:`, stamped once at the
|
|
575
|
+
machine level onto every one of that machine's own `prompt` states, never
|
|
576
|
+
authored on an individual state. It is passed to the agent CLI as (in the
|
|
577
|
+
reference driver's case) `--system-prompt`, which **replaces** the harness's own
|
|
578
|
+
default system prompt outright rather than appending to it (contrast with an
|
|
579
|
+
`--append-system-prompt`-shaped flag) — so a machine declaring `system:` loses
|
|
580
|
+
not only the harness's own tool-use instructions but also its dynamic per-turn
|
|
581
|
+
sections: current working directory, environment info, memory-path information,
|
|
582
|
+
git status. A workflow author reaching for `system:` for the first time is
|
|
583
|
+
therefore writing a complete replacement system prompt, not a tweak on top of
|
|
584
|
+
the harness's own. The scoping is machine-level for the same reason memory is
|
|
585
|
+
machine-scoped below: a machine is the unit of conversational identity, so its
|
|
586
|
+
system prompt — like its model tier — is one property of that identity, constant
|
|
587
|
+
across every turn of the one resumed session, never a per-state override. Like
|
|
588
|
+
`model:`/`file:`, a `system:` value prefixed `./` or `../` is inlined from the
|
|
589
|
+
declaring config file's own directory at compile time — for a user's own
|
|
590
|
+
`.gtdrc` `workflow:` config only; the bundled template ships no filesystem
|
|
591
|
+
references at all, since it has to work inside a single-file build. The bundled
|
|
592
|
+
default template ships six such personas already, one per prompt-bearing machine
|
|
593
|
+
— `designPersona`, `architectPersona`, `reviewerPersona`, `specReviewerPersona`,
|
|
594
|
+
`builderPersona`, `finisherPersona` — and, like any other bundled var, each is
|
|
595
|
+
overridable through a top-level `.gtdrc` `vars:` key or a `GTD_<NAME>`-style
|
|
596
|
+
environment variable (e.g. `GTD_DESIGNPERSONA`) — gtd's existing, generic
|
|
597
|
+
vars-override mechanism, nothing new.
|
|
598
|
+
|
|
492
599
|
Memory is **entry-scoped to a machine**, not a state-authored label: each
|
|
493
600
|
machine instance (a node in the `machines:` tree, e.g. `build`, `build.health`,
|
|
494
601
|
`packages.item`, `packages.item.health`) owns its own conversational scope, and
|
|
495
|
-
a `prompt`-content state's `memory` key — surfaced in `gtd next --json
|
|
496
|
-
`
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
`
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
`
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
scopes.
|
|
602
|
+
a `prompt`-content state's `memory` key — surfaced in `gtd next --json`'s
|
|
603
|
+
`memory` field, and as a `Memory: <key>` line in plain `gtd next` — is computed,
|
|
604
|
+
never authored, as `<scope>#<hash7>`: `<scope>` is that machine instance's
|
|
605
|
+
dotted path (the root instance is shown as `root`), and `<hash7>` anchors to the
|
|
606
|
+
commit the CURRENT unbroken entry into that scope started FROM. Entering a
|
|
607
|
+
**descendant** scope (e.g. dipping from `build` into `build.health`) does not
|
|
608
|
+
break the parent's unbroken run — a full agent turn in a nested child machine,
|
|
609
|
+
then back to the parent, still resumes the SAME parent conversation; entering a
|
|
610
|
+
**sibling or unrelated** scope does start a fresh one. The bundled template's
|
|
611
|
+
`build.review` (the human review tail) is a worked example: it is nested INSIDE
|
|
612
|
+
`build` (the builder's own machine) so that a `gtd --entry fix-precheck` run —
|
|
613
|
+
`build.fix` -> `build.health.check` -> `build.review.*` -> `build.squashing` —
|
|
614
|
+
stays inside one subtree, letting `build.squashing` resume the SAME session that
|
|
615
|
+
made the fixes instead of a root-level sibling forcing it to re-derive the range
|
|
616
|
+
from scratch on every pass through the tail. (An actionable review round breaks
|
|
617
|
+
the run on purpose instead — it leaves `build` entirely through a root-level
|
|
618
|
+
`re-unwind` state and a full re-plan, since a hand-edit made during review is a
|
|
619
|
+
sketch to reconsider, not a fix to build on.) Two instances of the same reusable
|
|
620
|
+
machine (e.g. `build.health` and `packages.item.health`, both instantiating
|
|
621
|
+
`healthGate`) get different scopes and so never share a key, even though they're
|
|
622
|
+
the "same shaped" machine. One consequence is a structural guarantee: **a
|
|
623
|
+
reviewer's turn never resumes an implementer's session, and vice versa** — a
|
|
624
|
+
reviewer machine and the implementer machine it reviews are always different
|
|
625
|
+
instances with different scopes.
|
|
520
626
|
|
|
521
627
|
`gtd` itself stores NOTHING to make this work: `session.id` is
|
|
522
628
|
`UUIDv5(<fixed gtd namespace>, <memory key>)` — a deterministic hash of the
|
|
@@ -550,7 +656,7 @@ reports `gtd`'s own lifecycle to that status without any herdr-specific
|
|
|
550
656
|
knowledge in `gtd` itself: `working` while the loop drives, `blocked` when it
|
|
551
657
|
comes to rest on a human (a gate, a stall, a non-zero exit), and `idle`
|
|
552
658
|
(rendered as "done" once the pane's tab is unfocused) when a run ends without
|
|
553
|
-
anything owed. It needs nothing from `gtd` beyond `gtd
|
|
659
|
+
anything owed. It needs nothing from `gtd` beyond `gtd next --sh`'s `gtd_actor`
|
|
554
660
|
field — a strictly read-only orientation peek.
|
|
555
661
|
|
|
556
662
|
Save this as `~/.local/bin/gtdh`, `chmod +x` it, and run `gtdh` in place of
|
|
@@ -560,8 +666,8 @@ Save this as `~/.local/bin/gtdh`, `chmod +x` it, and run `gtdh` in place of
|
|
|
560
666
|
#!/usr/bin/env bash
|
|
561
667
|
# Report gtd's own lifecycle to the herdr pane it runs in: working while the
|
|
562
668
|
# loop drives, blocked when it rests on you, idle (shown as "done") otherwise.
|
|
563
|
-
# Needs nothing from gtd but `gtd
|
|
564
|
-
# passthrough.
|
|
669
|
+
# Needs nothing from gtd but `gtd next --sh`. Outside herdr it is a plain
|
|
670
|
+
# passthrough.
|
|
565
671
|
set -uo pipefail
|
|
566
672
|
|
|
567
673
|
pane="${HERDR_PANE_ID:-}"
|
|
@@ -586,14 +692,16 @@ trap 'report blocked; exit 130' INT TERM
|
|
|
586
692
|
env -u HERDR_PANE_ID gtd-loop
|
|
587
693
|
rc=$?
|
|
588
694
|
|
|
589
|
-
# Whose turn is it now? `gtd
|
|
590
|
-
#
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
|
|
695
|
+
# Whose turn is it now? `gtd next --sh` is a strictly read-only peek — every
|
|
696
|
+
# gtd command is, including `gtd next` (its prompt session id is derived,
|
|
697
|
+
# never minted/stored) — only the emitted scripts a driver runs actually
|
|
698
|
+
# touch git. A human actor means gtd is waiting on you; anything else means
|
|
699
|
+
# the run ended with nothing owed. Assign first, THEN eval — same reason as
|
|
700
|
+
# the driver above.
|
|
701
|
+
out="$(gtd next --sh 2>/dev/null)" || out=""
|
|
702
|
+
eval "${out:-}" 2>/dev/null || true
|
|
595
703
|
|
|
596
|
-
if [ "$rc" -ne 0 ] || [ -z "$
|
|
704
|
+
if [ "$rc" -ne 0 ] || [ -z "${gtd_actor:-}" ] || [ "$gtd_actor" = human ]; then
|
|
597
705
|
report blocked
|
|
598
706
|
else
|
|
599
707
|
report idle
|
|
@@ -632,7 +740,7 @@ to build a driver without leaving your terminal. The briefing ends by directing
|
|
|
632
740
|
the agent to INTERVIEW you first — which agent CLI runs the turns, under what
|
|
633
741
|
permission model, how you want to invoke the loop, what should happen at a human
|
|
634
742
|
gate — and to build the driver your answers describe rather than copying the
|
|
635
|
-
reference
|
|
743
|
+
reference sh. It writes nothing: "install" means installing knowledge into the
|
|
636
744
|
calling agent's context, and the briefing it prints is always exactly as current
|
|
637
745
|
as the `gtd` that printed it.
|
|
638
746
|
|
|
@@ -641,88 +749,109 @@ privileged one — the engine itself is a supported public surface, and anything
|
|
|
641
749
|
below holds for any driver you write against it. gtd decides and prints; it
|
|
642
750
|
never touches git itself. The four commands that change anything — `gtd land`,
|
|
643
751
|
`gtd --entry <state>`, `gtd abandon`, and `gtd restore` — perform no git write
|
|
644
|
-
when run
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
752
|
+
when run: each one prints ONE POSIX sh script for YOU to execute
|
|
753
|
+
(`src/Emit.ts`'s `combinedScript`) — a leading comment ("gtd emitted this and
|
|
754
|
+
did NOT run it — pipe it into `sh` to land the turn"), then the REQUIRED half
|
|
755
|
+
verbatim, then, only when there's a presentation-only follow-up, a second
|
|
756
|
+
comment ("presentation only — safe to skip") and the OPTIONAL half wrapped in a
|
|
757
|
+
subshell whose own non-zero exit is swallowed (reported to stderr as a warning,
|
|
758
|
+
never turning a landed turn into a failing one). Printing gtd's output and never
|
|
759
|
+
running it is not driving anything; a driver must pipe or execute what gtd
|
|
760
|
+
prints — e.g. the capture-then-pipe form the reference driver below uses — never
|
|
761
|
+
a bare `gtd land | sh`, which would hand an empty script to a shell on a refusal
|
|
762
|
+
instead of stopping first (see the reference driver's own use of `gtd land --sh`
|
|
763
|
+
below).
|
|
764
|
+
|
|
765
|
+
Every script gtd emits — `gtd land`, `gtd --entry <state>`, `gtd abandon`,
|
|
766
|
+
`gtd restore`, and the format/validate script `gtd validate` prints — is POSIX
|
|
767
|
+
`sh`, portable to `dash`: a driver may run any of them with any POSIX-compliant
|
|
768
|
+
shell, not specifically bash. The same convention extends to the workflow's own
|
|
769
|
+
`vars.testCommand` (what a `script`-content check state actually executes): it
|
|
770
|
+
is expected to be POSIX sh-compatible too, but this is a DOCUMENTED CONVENTION
|
|
771
|
+
only — gtd never inspects or validates `testCommand`'s shell dialect itself, it
|
|
772
|
+
only renders the value into a script and hands it to whatever shell the driver
|
|
773
|
+
invokes that script with.
|
|
774
|
+
|
|
775
|
+
- **The required half** is everything that decides what lands in git — closing
|
|
776
|
+
an open review checkout window, the resting state's own steering-mode
|
|
657
777
|
`format:`/`validate:` commands, the commit or squash itself (`gtd land` and
|
|
658
778
|
`gtd --entry <state>`), or the ref update and reset that undo a process
|
|
659
779
|
(`gtd abandon`, `gtd restore`) — and, last, a printed line naming what just
|
|
660
780
|
landed (`src/OutcomeScript.ts`'s `gtd_report_*` calls): a transition or
|
|
661
781
|
capture's changed-file rows, or the abandon/restore prose, resolved from the
|
|
662
|
-
repository AFTER the write above it.
|
|
663
|
-
never lands, and you never see what it
|
|
664
|
-
|
|
665
|
-
|
|
782
|
+
repository AFTER the write above it. Its own exit code IS the printed script's
|
|
783
|
+
exit code — skipping it means the turn never lands, and you never see what it
|
|
784
|
+
did.
|
|
785
|
+
- **The optional half** is presentation only: re-opening the review checkout
|
|
786
|
+
window (the `<<<<<<< HEAD` diff view) after `gtd land` lands at a
|
|
666
787
|
`reviewWindow: true` state, so an editor's diff view has something to show.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
788
|
+
Its own failure is swallowed by the wrapping subshell (a warning on stderr,
|
|
789
|
+
nothing more) — skip it (or let it fail) and you lose nothing but that view,
|
|
790
|
+
the workflow is still driven correctly. `gtd abandon`/`gtd restore` never
|
|
791
|
+
carry one — there is no window to reopen after either, so their printed
|
|
792
|
+
artifact is the required half alone, with no trailing comment/subshell at all.
|
|
670
793
|
|
|
671
794
|
`gtd next --json` carries one more field worth a custom driver's attention:
|
|
672
795
|
|
|
673
796
|
- **`log`** — the per-worktree loop log path, always present. It's derived from
|
|
674
797
|
the worktree's own git dir, so two worktrees looping concurrently never share
|
|
675
|
-
one file; set `$GTD_LOOP_LOG` to override it verbatim
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
798
|
+
one file; set `$GTD_LOOP_LOG` to override it verbatim, or `$GIT_DIR` to name
|
|
799
|
+
the git dir directly (both honored the same way every git subprocess already
|
|
800
|
+
honors them — an inherited `$GIT_DIR` moves the log path along with it). gtd
|
|
801
|
+
itself neither creates nor truncates this file — a driver appends subprocess
|
|
802
|
+
output to it and truncates once at the start of a run, exactly like the driver
|
|
803
|
+
above does.
|
|
804
|
+
|
|
805
|
+
Even a genuine no-op `gtd land` (a clean tree matching no `on` pattern) prints a
|
|
806
|
+
PRINT-ONLY script: no git write, just the same `nothing to do at "<state>"` line
|
|
807
|
+
the script prints when a driver runs it. Every encoding (plain, `--json`,
|
|
808
|
+
`--sh`) carries that same script verbatim in its `script` field — running it is
|
|
809
|
+
never optional, whichever encoding a driver reads it from. The script is
|
|
810
|
+
self-contained (it carries its own precondition assert and retry helper) and
|
|
811
|
+
safe to run standalone, in sequence, or not at all — paste it into a terminal
|
|
812
|
+
and it does exactly what it says, printed output included: it detects its own
|
|
813
|
+
tty/`NO_COLOR` at RUN time (not at the moment gtd generated it), so the
|
|
814
|
+
fancy/plain rendering always matches wherever you actually run it.
|
|
690
815
|
|
|
691
816
|
A driver has no opening move. It does not need to know whose turn it is before
|
|
692
|
-
it starts — `gtd next --json` tells it, and a human's pending edit
|
|
693
|
-
ordinary `kind: "capture"` beat that the driver lands like any
|
|
694
|
-
this replaces: never `gtd land` outside a beat you dispatched. A
|
|
695
|
-
clean `prompt` rest authors an empty attempt on purpose (that IS
|
|
696
|
-
bookkeeping), so an unconditional opening land would manufacture a
|
|
697
|
-
a fresh start.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
`
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
`
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
817
|
+
it starts — `gtd next --json`/`--sh` tells it, and a human's pending edit
|
|
818
|
+
arrives as an ordinary `kind: "capture"` beat that the driver lands like any
|
|
819
|
+
other. The rule this replaces: never `gtd land` outside a beat you dispatched. A
|
|
820
|
+
stray land at a clean `prompt` rest authors an empty attempt on purpose (that IS
|
|
821
|
+
the stall bookkeeping), so an unconditional opening land would manufacture a
|
|
822
|
+
stall out of a fresh start. The one EXCEPTION is the run's very first beat: land
|
|
823
|
+
it before trusting `idle` there too, so a workflow whose initial state declares
|
|
824
|
+
its own clean-tree `"C"` pattern still gets a chance to advance on a human's
|
|
825
|
+
bare re-invocation, rather than the driver concluding "nothing owed" without
|
|
826
|
+
ever giving that edge a turn (see the reference driver's own comment on this).
|
|
827
|
+
|
|
828
|
+
Exit code carries none of this any more — every command, `next`/`land` included,
|
|
829
|
+
exits `0` on success (see [Exit codes](#exit-codes) above). Whose turn is next,
|
|
830
|
+
and whether a landing settles, are both PLAIN FIELDS on `gtd next --json`/`--sh`
|
|
831
|
+
and `gtd land --json`/`--sh`: `next`'s own `kind`
|
|
832
|
+
(`capture`/`message`/`script`/`prompt`/`stalled`) and `idle`; `land`'s own
|
|
833
|
+
`settled` and `idle`. `gtd land --sh` carries `settled`/`idle` alongside the
|
|
834
|
+
script itself, so ONE invocation both runs the landing and tells the driver
|
|
835
|
+
whether to stop — no second read needed to decide. A no-op at a `script` rest
|
|
836
|
+
settles right where it rests (stop immediately, nothing more to read); an
|
|
837
|
+
ordinary or squash landing that finishes the whole process instead resolves
|
|
838
|
+
`idle` on the FOLLOWING `gtd next` — the reference driver below reads once more
|
|
839
|
+
only to DISPLAY that gate's message, never to decide whether to stop. Declaring
|
|
840
|
+
a `C` edge on a `script` state is the workflow-side way to make the state
|
|
841
|
+
advance instead of settling.
|
|
842
|
+
|
|
843
|
+
### Drivers other than sh
|
|
844
|
+
|
|
845
|
+
The protocol above is JSON in (from `gtd next --json` alone), subprocesses out —
|
|
846
|
+
sh is one convenient shape, not the only one. A **program** parses
|
|
847
|
+
`gtd next --json`, switches on `kind`, and runs `gtd next`'s (plain-text) output
|
|
848
|
+
and `gtd land`'s (POSIX sh) output as subprocesses — no sh anywhere in it. A
|
|
849
|
+
**human** runs plain `gtd next` (there is no JSON form of it at all), does what
|
|
850
|
+
it says, and pastes `gtd land | sh`: plain-text output exists for exactly this,
|
|
851
|
+
printing the combined script ready to paste. An **agent** gets `gtd install` as
|
|
852
|
+
its instructions — the same protocol, in a form built to be pasted into a
|
|
853
|
+
context window rather than read. A **CI job** is the program case with the
|
|
854
|
+
`prompt` arm pointed at a headless agent CLI, and
|
|
726
855
|
`kind: "message"`/`kind: "stalled"` mapped onto "stop and report".
|
|
727
856
|
|
|
728
857
|
### A complete minimal driver
|
|
@@ -737,102 +866,173 @@ This exact fenced block is extracted and executed by
|
|
|
737
866
|
`tests/integration/features/readme-driver.feature` — renaming the heading above
|
|
738
867
|
or splitting the block into more than one fence breaks the build.
|
|
739
868
|
|
|
869
|
+
**Pipe the prompt to the agent CLI on stdin — never pass it as a command-line
|
|
870
|
+
argument.** A `prompt` beat's content embeds a full diff, and argv is capped:
|
|
871
|
+
roughly 1 MB on macOS, and POSIX guarantees only 4 KB (`ARG_MAX`). Both are
|
|
872
|
+
reachable by an ordinary diff, so a driver that passes the prompt as an argument
|
|
873
|
+
works in testing and then fails on the first large change, in a way that looks
|
|
874
|
+
like an agent error rather than a driver bug. The driver below pipes `$c`/`$out`
|
|
875
|
+
into `claude -p` over stdin for exactly this reason.
|
|
876
|
+
|
|
740
877
|
```bash
|
|
741
|
-
#!/usr/bin/env
|
|
742
|
-
set -
|
|
743
|
-
|
|
744
|
-
# The bundle plans; this executes. Emitted scripts print their own outcomes.
|
|
745
|
-
# Exit 3 means SETTLED — nothing owed — so this exits the whole driver at 0.
|
|
746
|
-
gtd_land() {
|
|
747
|
-
local json code=0
|
|
748
|
-
json="$(gtd land --json)" || code=$?
|
|
749
|
-
[ "$code" = 0 ] || [ "$code" = 3 ] || return "$code"
|
|
750
|
-
bash -c "$(jq -r '.required // empty' <<<"$json")" || return $?
|
|
751
|
-
bash -c "$(jq -r '.optional // empty' <<<"$json")" ||
|
|
752
|
-
echo "warn: presentation follow-up failed — continuing" >&2
|
|
753
|
-
[ "$code" = 3 ] && exit 0
|
|
754
|
-
return 0
|
|
755
|
-
}
|
|
878
|
+
#!/usr/bin/env sh
|
|
879
|
+
set -eu
|
|
756
880
|
|
|
757
881
|
beat=1
|
|
758
882
|
while :; do
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
883
|
+
# One invocation per beat: assign first, THEN eval — command substitution
|
|
884
|
+
# inside eval's own argument would swallow a failed `gtd next` under
|
|
885
|
+
# `set -e` (eval would see only the empty string and abort on some later
|
|
886
|
+
# unset variable with a confusing message). Assigning to `out` first makes
|
|
887
|
+
# this a simple command whose own exit status IS the substitution's, so
|
|
888
|
+
# `set -e` aborts correctly on a genuine failure.
|
|
889
|
+
out="$(gtd next --sh)"
|
|
890
|
+
eval "$out"
|
|
891
|
+
|
|
892
|
+
# `gtd_idle` (true iff the initial state, clean tree) is the one shape
|
|
893
|
+
# that means the process is genuinely done — EXCEPT on the run's opening
|
|
894
|
+
# beat: land it anyway, so a workflow whose initial state declares its own
|
|
895
|
+
# clean-tree "C" pattern still gets a chance to fire.
|
|
896
|
+
if [ "$beat" -gt 1 ] && [ "${gtd_idle:-}" = true ]; then
|
|
897
|
+
gtd next
|
|
898
|
+
exit 0
|
|
899
|
+
fi
|
|
900
|
+
|
|
901
|
+
case "$gtd_kind" in
|
|
902
|
+
stalled) printf '%s\n' "$gtd_content" >&2; exit 1 ;;
|
|
764
903
|
# you re-ran us resting here: you either edited something or accepted by
|
|
765
904
|
# editing nothing, so land the opening beat either way. Later beats are
|
|
766
905
|
# gates we just produced and you have not read yet — hand off.
|
|
767
|
-
message) [ "$beat" = 1 ] || {
|
|
906
|
+
message) [ "$beat" = 1 ] || { gtd next; exit 0; } ;;
|
|
768
907
|
capture) ;; # the human already acted — just land it
|
|
769
|
-
script)
|
|
908
|
+
script)
|
|
909
|
+
sh -c "$gtd_content" >>"$gtd_log" 2>&1 || true ;;
|
|
770
910
|
prompt)
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
911
|
+
# $gtd_content embeds a full diff, so it goes to the agent over
|
|
912
|
+
# stdin, never as an argv positional — argv is capped (~1 MB on
|
|
913
|
+
# macOS, and POSIX guarantees only 4 KB, ARG_MAX), and a diff crosses
|
|
914
|
+
# that far sooner than you'd expect.
|
|
915
|
+
agent_turn() { printf '%s' "$gtd_content" | claude -p "$1" "$gtd_session_id" \
|
|
916
|
+
${gtd_model:+--model "$gtd_model"} \
|
|
917
|
+
${gtd_system:+--system-prompt "$gtd_system"} \
|
|
918
|
+
--dangerously-skip-permissions >>"$gtd_log" 2>&1; }
|
|
919
|
+
if [ "${gtd_session_resume:-}" = true ]
|
|
776
920
|
then agent_turn --resume || agent_turn --session-id
|
|
777
921
|
else agent_turn --session-id || agent_turn --resume
|
|
778
922
|
fi
|
|
779
|
-
|
|
780
|
-
while [ -n "$
|
|
781
|
-
n=$((n + 1)) && [ "$n" -gt 3 ] && { printf '%s\n' "$
|
|
782
|
-
|
|
783
|
-
|
|
923
|
+
n=0
|
|
924
|
+
while [ -n "${gtd_validate:-}" ] && ! fix="$(sh -c "$gtd_validate" 2>&1)"; do
|
|
925
|
+
n=$((n + 1)) && [ "$n" -gt 3 ] && { printf '%s\n' "$fix" >&2; exit 1; }
|
|
926
|
+
# $fix IS the fix prompt, verbatim — piped for the same reason as
|
|
927
|
+
# $gtd_content above. Whether `claude --resume` re-applies the
|
|
928
|
+
# original session's model/system prompt is a harness detail gtd
|
|
929
|
+
# cannot verify from outside, so this passes the identical
|
|
930
|
+
# $gtd_model/$gtd_system on both calls rather than assume it does —
|
|
931
|
+
# otherwise this fix turn might silently fall back to Claude Code's
|
|
932
|
+
# own defaults while the turn that produced the file ran under the
|
|
933
|
+
# workflow's own model and persona.
|
|
934
|
+
printf '%s' "$fix" | claude -p --resume "$gtd_session_id" \
|
|
935
|
+
${gtd_model:+--model "$gtd_model"} \
|
|
936
|
+
${gtd_system:+--system-prompt "$gtd_system"} \
|
|
937
|
+
--dangerously-skip-permissions >>"$gtd_log" 2>&1
|
|
784
938
|
done ;;
|
|
785
939
|
esac
|
|
786
|
-
|
|
940
|
+
|
|
941
|
+
# `gtd land --sh` carries `settled`/`idle` alongside the script itself —
|
|
942
|
+
# one invocation tells us both what to run and whether to stop, with no
|
|
943
|
+
# second read needed to decide either.
|
|
944
|
+
out="$(gtd land --sh)"
|
|
945
|
+
eval "$out"
|
|
946
|
+
printf '%s\n' "$gtd_script" | sh
|
|
947
|
+
if [ "${gtd_settled:-}" = true ]; then
|
|
948
|
+
[ "${gtd_idle:-}" = true ] && gtd next
|
|
949
|
+
exit 0
|
|
950
|
+
fi
|
|
787
951
|
beat=$((beat + 1))
|
|
788
952
|
done
|
|
789
953
|
```
|
|
790
954
|
|
|
791
|
-
Line by line it is the protocol described above:
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
955
|
+
Line by line it is the protocol described above: `out="$(gtd next --sh)"` then
|
|
956
|
+
`eval "$out"` is one invocation per beat — assigning first is what lets `set -e`
|
|
957
|
+
abort correctly on a genuine failure, where `eval "$(gtd next --sh)"` would
|
|
958
|
+
instead eval the empty string and die later on some unrelated unset variable;
|
|
959
|
+
the driver carries no `unset` of its own, since the `--sh` document's own
|
|
960
|
+
preamble makes `eval` self-contained. The very first beat lands even when
|
|
961
|
+
`gtd_idle` is already true, so a workflow whose initial state declares its own
|
|
962
|
+
clean-tree `"C"` pattern still gets a chance to fire before the driver calls it
|
|
963
|
+
done. `kind: "stalled"` prints `$gtd_content` (the diagnosis itself) to stderr
|
|
964
|
+
and stops — no re-invocation needed, since the beat already fetched it;
|
|
965
|
+
`message` halts unless it is the opening beat, which the human's own
|
|
966
|
+
re-invocation authored and which therefore lands like any other decision (see
|
|
967
|
+
[Driving the loop](#driving-the-loop) above — this is the one place the driver,
|
|
968
|
+
not gtd, decides, because "has the human read this gate" is run-scoped knowledge
|
|
969
|
+
gtd deliberately does not keep); `capture` lands a human's already-made edit
|
|
970
|
+
outright, no display needed; `script` runs `$gtd_content` in the driver;
|
|
971
|
+
`prompt` sends it to the agent over stdin with
|
|
972
|
+
`$gtd_session_id`/`$gtd_model`/`$gtd_system` mapped onto the agent's session
|
|
973
|
+
flags — trying `resume`'s hinted flag first and falling back to the other on
|
|
974
|
+
failure, since the session id is derived, not remembered (see
|
|
975
|
+
[Driving the loop](#driving-the-loop) above) — and its own `$gtd_validate`
|
|
805
976
|
script's output re-prompted verbatim on failure (the driver owns only the retry
|
|
806
|
-
cap)
|
|
807
|
-
|
|
808
|
-
|
|
977
|
+
cap). Every optional variable (`$gtd_model`, `$gtd_system`, `$gtd_validate`,
|
|
978
|
+
`$gtd_session_resume`, `$gtd_idle`, `$gtd_settled`) is read as `${var:-}` or
|
|
979
|
+
`${var:+...}` — under `set -u`, the `--sh` document's own `unset` preamble makes
|
|
980
|
+
an absent field genuinely unset, not empty, so a bare `$gtd_model` would abort
|
|
981
|
+
the driver. Every landed turn is executed — and reported — by the emitted
|
|
982
|
+
scripts themselves, via `gtd land --sh`'s own `$gtd_script` field, captured then
|
|
983
|
+
piped to `sh`. `$gtd_settled` ends a run that has nothing left to do: a no-op at
|
|
984
|
+
a `script` rest settles right where it rests (stop immediately, nothing more to
|
|
985
|
+
read), while an ordinary or squash landing that finishes the whole process
|
|
986
|
+
instead resolves `$gtd_idle` on the FOLLOWING beat's own `gtd next --sh` read —
|
|
987
|
+
the reference driver reads once more (a plain `gtd next`) only to DISPLAY that
|
|
988
|
+
gate's own message, the decision to stop already made from `$gtd_settled`/
|
|
989
|
+
`$gtd_idle` alone.
|
|
990
|
+
|
|
991
|
+
This paste passes `$gtd_system` on argv, and POSIX guarantees only 4 KB of argv
|
|
992
|
+
(`ARG_MAX`) — real systems are far higher (roughly 1 MB on both macOS and
|
|
993
|
+
Linux), and the bundled personas are only a few KB each, so this works
|
|
994
|
+
everywhere in practice. A driver shipping much larger system prompts, or
|
|
995
|
+
targeting a platform sitting at the POSIX floor, should write the string to a
|
|
996
|
+
file and pass that instead: gtd's own contract ends at emitting `gtd_system` as
|
|
997
|
+
a string, and how a driver hands that string to its agent CLI — argv, a temp
|
|
998
|
+
file, or something else — is the driver's own call, since it alone knows its
|
|
999
|
+
platform's argv limit and whether it has a writable temp directory; gtd adds no
|
|
1000
|
+
flag, no file, and no mechanism of its own for this. (The doc-test below only
|
|
1001
|
+
proves this paste parses and runs against a `claude` shim — not that the flag is
|
|
1002
|
+
spelled or placed correctly; that's a `claude --help` check plus one live run,
|
|
1003
|
+
not something the green suite can catch.)
|
|
809
1004
|
|
|
810
1005
|
### The self-validation gate
|
|
811
1006
|
|
|
812
1007
|
After an agent turn at a state that declares `file:`+`mode:`, run the script:
|
|
813
1008
|
either `gtd next --json`'s own embedded `.validate` field (present at every
|
|
814
1009
|
`prompt` beat that hands over a validatable file), or, standalone,
|
|
815
|
-
`gtd validate
|
|
816
|
-
shared resolver. Exit 0 means the file is well-formed — proceed to
|
|
817
|
-
non-zero exit means the script's own captured output IS a
|
|
818
|
-
ready-to-send fix prompt (an instruction plus the findings, see
|
|
819
|
-
`failurePromptWrapper`): send it back to the same agent session
|
|
820
|
-
cap how many fix attempts you allow yourself — the driver owns
|
|
821
|
-
not gtd. Landing never TRUSTS that you validated, either: the
|
|
822
|
-
script carries the same format/validate commands ahead of its own
|
|
823
|
-
fails without committing when they fail, so a malformed file is never
|
|
824
|
-
whether or not you ran the validate script first.
|
|
1010
|
+
`gtd validate`'s own plain-text output — both resolve the exact same script from
|
|
1011
|
+
one shared resolver. Exit 0 means the file is well-formed — proceed to
|
|
1012
|
+
`gtd land`. A non-zero exit means the script's own captured output IS a
|
|
1013
|
+
complete, ready-to-send fix prompt (an instruction plus the findings, see
|
|
1014
|
+
`src/Emit.ts`'s `failurePromptWrapper`): send it back to the same agent session
|
|
1015
|
+
verbatim, and cap how many fix attempts you allow yourself — the driver owns
|
|
1016
|
+
that retry count, not gtd. Landing never TRUSTS that you validated, either: the
|
|
1017
|
+
emitted `land` script carries the same format/validate commands ahead of its own
|
|
1018
|
+
commit and fails without committing when they fail, so a malformed file is never
|
|
1019
|
+
captured whether or not you ran the validate script first.
|
|
825
1020
|
|
|
826
1021
|
### Failure taxonomy and recovery
|
|
827
1022
|
|
|
828
|
-
|
|
829
|
-
non-zero-looking
|
|
1023
|
+
Several different things can go wrong, and they mean different things — and most
|
|
1024
|
+
non-zero-looking exits are not a failure at all:
|
|
830
1025
|
|
|
831
1026
|
- **`gtd` itself exits 1.** Nothing was attempted — this is a refusal (a guard
|
|
832
|
-
rejected the turn)
|
|
833
|
-
- **`gtd
|
|
834
|
-
|
|
835
|
-
|
|
1027
|
+
rejected the turn). No script was ever produced.
|
|
1028
|
+
- **`gtd` itself exits 2.** A usage error — nothing was even attempted, the
|
|
1029
|
+
invocation itself was wrong (unknown option/command, bad arity, a scope
|
|
1030
|
+
violation).
|
|
1031
|
+
- **`gtd land` succeeds (exit 0) but doesn't settle.** This is NOT a failure:
|
|
1032
|
+
whose turn is next lives in the FOLLOWING `gtd next`'s own `kind` field, not
|
|
1033
|
+
in `gtd land`'s exit code (see [Exit codes](#exit-codes)) — and `gtd land`'s
|
|
1034
|
+
own stdout still carries a script (a print-only note, or a genuine
|
|
1035
|
+
retain+rewind, or an ordinary commit) that a driver must still run.
|
|
836
1036
|
- **An emitted script exits non-zero when YOU run it.** Something may have
|
|
837
1037
|
partially happened — e.g. a `gtd_retry`-wrapped git write landed but a later
|
|
838
1038
|
step in the same script failed.
|
|
@@ -856,8 +1056,10 @@ its own retry/resume logic beyond "if the script failed, ask gtd again."
|
|
|
856
1056
|
|
|
857
1057
|
### Prerequisites
|
|
858
1058
|
|
|
859
|
-
-
|
|
860
|
-
|
|
1059
|
+
- **A POSIX `sh`** (dash, ash, bash's own POSIX mode, etc.) — gtd's own emitted
|
|
1060
|
+
scripts (`gtd land`, `gtd --entry <state>`, `gtd abandon`, `gtd restore`) are
|
|
1061
|
+
POSIX sh; captured, then piped into it (see
|
|
1062
|
+
[Writing your own driver](#writing-your-own-driver) above).
|
|
861
1063
|
- **`gtd` on `PATH`** — a mode's seeded `validate:` command (the one the
|
|
862
1064
|
compiler fills in for the built-in `qa`/`review` formats) is literally the
|
|
863
1065
|
string `gtd check <mode> '<file>'`, invoked by NAME from inside an emitted
|
|
@@ -896,7 +1098,7 @@ One case never runs your `format:` (or `validate:`) at all: a step whose diff
|
|
|
896
1098
|
DELETES the state's own `file:`. Deleting it is a legitimate outcome — a review
|
|
897
1099
|
sign-off's whole diff is the review doc's deletion — and there is nothing left
|
|
898
1100
|
to format. Emitting the command anyway would make such a step unlandable, since
|
|
899
|
-
`format:` is the first line of a `set -
|
|
1101
|
+
`format:` is the first line of a `set -eu` script and a formatter like
|
|
900
1102
|
`prettier --write` exits non-zero on a path that is not there.
|
|
901
1103
|
|
|
902
1104
|
### Built-in steering formats are ordinary modes
|
|
@@ -977,7 +1179,6 @@ workflow:
|
|
|
977
1179
|
<name>:
|
|
978
1180
|
format: <shell command> # both optional — {} is the format-only tier
|
|
979
1181
|
validate: <shell command>
|
|
980
|
-
stateDir: <string> # optional, an Eta template naming where this workflow keeps its own plumbing (default ".gtd") — the definition-level declaration the engine reads, distinct from the `vars.stateDir` var the bundled template renders it from
|
|
981
1182
|
entry:
|
|
982
1183
|
default: <machine name> # which machine is the ROOT instance
|
|
983
1184
|
machines:
|
|
@@ -1002,8 +1203,8 @@ workflow:
|
|
|
1002
1203
|
retry:
|
|
1003
1204
|
max: <number>
|
|
1004
1205
|
otherwise: <targetState>
|
|
1005
|
-
label: <string> # optional, opaque display name passed through `gtd next --json
|
|
1006
|
-
file: <string> # optional, an Eta template naming the state's steering file
|
|
1206
|
+
label: <string> # optional, opaque display name passed through `gtd next --json`
|
|
1207
|
+
file: <string> # optional, an Eta template naming the state's steering file RELATIVE to ".gtd/" — the compiler prepends that directory automatically
|
|
1007
1208
|
mode: <modeName> # optional, requires "file" — must be declared in `modes:` (qa/review are seeded for you; everything else, including prose, you declare)
|
|
1008
1209
|
reviewWindow: true # optional — open the review checkout window at rest here
|
|
1009
1210
|
reviewBase: true # optional — anchor the review window's diff base to this state's most-recent commit
|
|
@@ -1023,11 +1224,7 @@ derives a driver's session id).
|
|
|
1023
1224
|
The top-level `entry:` key (naming the root machine, `entry.default`) and a
|
|
1024
1225
|
state's own `entry: true` flag are the same word at two different levels, by
|
|
1025
1226
|
design: one selects the workflow's root machine, the other opts one state in as
|
|
1026
|
-
an extra manual entry point.
|
|
1027
|
-
`vars.stateDir` var it renders from are the same call: the definition-level
|
|
1028
|
-
declaration is what the engine reads (e.g. to tell gtd's own plumbing apart from
|
|
1029
|
-
your code), while the var is the knob you actually override — repointing
|
|
1030
|
-
`vars.stateDir` moves both.
|
|
1227
|
+
an extra manual entry point.
|
|
1031
1228
|
|
|
1032
1229
|
A workflow is authored as a TREE of reusable, parameterized machines — a
|
|
1033
1230
|
gate/loop written once and instantiated several times with different `with:`
|
|
@@ -1060,8 +1257,8 @@ Besides `it.vars` (below), a `script`/`prompt`/`message`/`commit` template sees:
|
|
|
1060
1257
|
|
|
1061
1258
|
A template never sees rendered diff CONTENT — no field carries a diff. It names
|
|
1062
1259
|
a base and leaves the agent to run `git diff <base>` itself; this keeps every
|
|
1063
|
-
render cheap (no diff computed on `gtd next`/`gtd
|
|
1064
|
-
|
|
1260
|
+
render cheap (no diff computed on `gtd next`/`gtd lsp`) and the prompt small and
|
|
1261
|
+
cacheable.
|
|
1065
1262
|
|
|
1066
1263
|
Authoring or editing a workflow with a coding agent? `skills/authoring/SKILL.md`
|
|
1067
1264
|
is the agent-facing contract for producing a valid `workflow:` — the state
|
|
@@ -1222,10 +1419,10 @@ top-level `.gtdrc` `vars:` key, or the matching `GTD_STYLEBLOCK` /
|
|
|
1222
1419
|
`GTD_STYLEFORMATCONTRACT` environment variable):
|
|
1223
1420
|
|
|
1224
1421
|
- **`styleBlock`** — the voice itself, injected at all seven prompt states that
|
|
1225
|
-
generate content, not just three: the free-prose ones — the
|
|
1226
|
-
package files (`architecture.decompose`), `
|
|
1227
|
-
(`packages.item.spec.review`), and `
|
|
1228
|
-
the four machine-parsed states named in the next bullet. Blanking
|
|
1422
|
+
generate content, not just three: the free-prose ones — the `.gtd/packages/`
|
|
1423
|
+
package files (`architecture.decompose`), `.gtd/SPEC_FEEDBACK.md`
|
|
1424
|
+
(`packages.item.spec.review`), and `.gtd/COMMIT_MSG.md` (`build.squashing`) —
|
|
1425
|
+
plus the four machine-parsed states named in the next bullet. Blanking
|
|
1229
1426
|
`GTD_STYLEBLOCK` strips the voice from all seven, including the machine-parsed
|
|
1230
1427
|
ones, not only the free-prose three. In short: why density matters before any
|
|
1231
1428
|
rule, answer-first with no preamble or restatement, blunt and imperative,
|
|
@@ -1238,17 +1435,17 @@ top-level `.gtdrc` `vars:` key, or the matching `GTD_STYLEBLOCK` /
|
|
|
1238
1435
|
ahead of the role sentence and well before any state-specific format-contract
|
|
1239
1436
|
text — `styleBlock` first, then `styleFormatContract`, at the four prompt
|
|
1240
1437
|
states whose output a parser reads: the two `qa`-mode steering files
|
|
1241
|
-
(`
|
|
1242
|
-
`architecture.author`) and the `review`-mode one (`
|
|
1243
|
-
`build.review.reviewing`), plus `
|
|
1438
|
+
(`.gtd/REQUIREMENTS.md` at `design.triage`, `.gtd/ARCHITECTURE.md` at
|
|
1439
|
+
`architecture.author`) and the `review`-mode one (`.gtd/REVIEW.md` at
|
|
1440
|
+
`build.review.reviewing`), plus `.gtd/REQUIREMENTS.md` again at
|
|
1244
1441
|
`build.review.collecting`, which classifies a review round straight into it.
|
|
1245
1442
|
|
|
1246
1443
|
Three more generated files carry no injected voice, because a script — not an
|
|
1247
|
-
agent — writes them: `
|
|
1248
|
-
stamp — the tool's content, not gtd's prose), `
|
|
1249
|
-
to style), and `
|
|
1250
|
-
directly in `build.review.deciding`'s script rather than templated in
|
|
1251
|
-
either variable).
|
|
1444
|
+
agent — writes them: `.gtd/FEEDBACK.md` (verbatim test-suite output plus a HEAD
|
|
1445
|
+
stamp — the tool's content, not gtd's prose), `.gtd/NEXT.md` (a bare path, no
|
|
1446
|
+
prose to style), and `.gtd/REVIEW_RAW.md` (gtd's own prose, hand-tightened in
|
|
1447
|
+
the voice directly in `build.review.deciding`'s script rather than templated in
|
|
1448
|
+
through either variable).
|
|
1252
1449
|
|
|
1253
1450
|
### Lookup and precedence
|
|
1254
1451
|
|
|
@@ -1323,8 +1520,8 @@ option"/"uncheck this option" code actions on each option — offered anywhere o
|
|
|
1323
1520
|
the option's list item, including any wrapped continuation lines, not just its
|
|
1324
1521
|
own `- [ ]` line — diagnostics for both (live as you edit), and a
|
|
1325
1522
|
`gtd.openSteeringFile` command that jumps to the current state's steering file —
|
|
1326
|
-
including `idle`, whose `
|
|
1327
|
-
before a process has started. The command only names that path — it never
|
|
1523
|
+
including `idle`, whose `.gtd/TODO.md` hint gives the one keybinding an answer
|
|
1524
|
+
even before a process has started. The command only names that path — it never
|
|
1328
1525
|
stat-checks or creates it — so on a repo that has never run gtd, `.gtd/` may not
|
|
1329
1526
|
exist yet and editors differ on opening a file whose parent directory is missing
|
|
1330
1527
|
(an empty buffer that creates it on save vs. refusing the path outright); this
|