@pmelab/gtd 9.10.2 → 9.11.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 +47 -1597
- package/dist/gtd.bundle.mjs +1035 -3015
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,11 +7,41 @@
|
|
|
7
7
|
|
|
8
8
|
**gtd** is a git-aware CLI that derives the entire agentic workflow — capture,
|
|
9
9
|
plan, build, check, review — from your repository state, and prints the next
|
|
10
|
-
prompt for whatever agent you point at it. Every turn is a commit
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
prompt for whatever agent you point at it. Every turn is a commit, so the
|
|
11
|
+
process survives a crash, a reboot, or a week away, and travels with the branch
|
|
12
|
+
to any machine that checks it out.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @pmelab/gtd
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or run without installing (prefix every `gtd` below with `npx`) — see
|
|
21
|
+
[Configuration](https://github.com/pmelab/gtd/blob/main/docs/configuration.md)
|
|
22
|
+
for the settings most projects tune.
|
|
23
|
+
|
|
24
|
+
## First run
|
|
25
|
+
|
|
26
|
+
Paste this into your coding agent:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
!gtd install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
It prints a self-contained briefing that teaches the agent to set the project
|
|
33
|
+
up, build two commands for itself in whatever shell or runtime it runs, and ask
|
|
34
|
+
you what you want before it starts driving. You get one prompt to paste, not a
|
|
35
|
+
state name to choose. The two commands: a **loop command** that drives beats
|
|
36
|
+
until the process rests, and an **edit command** that opens the steering file
|
|
37
|
+
the process is waiting on right now — falling back to `.gtd/TODO.md` when the
|
|
38
|
+
resting state declares none. That fallback is also how you begin: on a clean
|
|
39
|
+
repository the edit command opens the empty `.gtd/TODO.md`, and whatever you
|
|
40
|
+
write there is the first sketch the whole process gets planned from.
|
|
41
|
+
|
|
42
|
+
Writing a driver by hand instead? See
|
|
43
|
+
[Driving the loop](https://github.com/pmelab/gtd/blob/main/docs/driver.md) for
|
|
44
|
+
the full protocol and a complete minimal reference implementation.
|
|
15
45
|
|
|
16
46
|
## Why
|
|
17
47
|
|
|
@@ -28,1611 +58,31 @@ No chat scrollback. No lost sessions. No infinite fix loops. Just git.
|
|
|
28
58
|
- **Harness agnostic.** gtd emits prompts to stdout (or JSON). Claude Code, a
|
|
29
59
|
bash loop, a CI job, or you reading it out loud — the workflow doesn't care
|
|
30
60
|
who executes it.
|
|
31
|
-
- **Bounded, not runaway.** Fix attempts are capped (`retry` on a state). When
|
|
32
|
-
the cap is hit, gtd redirects to a human gate instead of burning tokens
|
|
33
|
-
rewriting the same test for the 47th time.
|
|
34
|
-
- **Your call on history.** Every intermediate `gtd(actor): from → to` commit is
|
|
35
|
-
a real, attributed commit — the subject names both the state the work was done
|
|
36
|
-
in and where it advanced to, nothing hidden in chat. Squash them into one
|
|
37
|
-
conventional commit if you want that (an interactive rebase, an amend, a PR's
|
|
38
|
-
squash-merge, or a custom workflow with a `commit:` finale), or don't — gtd
|
|
39
|
-
makes no assumption.
|
|
40
|
-
|
|
41
|
-
## Install
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npm install -g @pmelab/gtd
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Or run without installing (prefix every `gtd` below with `npx`):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npx @pmelab/gtd next
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
That's it — gtd ships the **unified** workflow as its **built-in default**, so a
|
|
54
|
-
state command works out of the box with no configuration at all.
|
|
55
|
-
|
|
56
|
-
Optionally, seed the settings a project usually tunes — run once:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
gtd init
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
This writes a minimal `.gtdrc.json` seeding the one variable most projects
|
|
63
|
-
change — the test command (`vars.testCommand`, defaulting to `npm test`) — plus
|
|
64
|
-
a top-level `modes:` block suggesting **Prettier** as the steering-file
|
|
65
|
-
formatter (`npx prettier --write` for the built-in `qa`/`review` modes — format
|
|
66
|
-
only, so gtd still validates them); edit or drop either freely (point
|
|
67
|
-
`testCommand` at your suite, swap Prettier for dprint or a script, delete a
|
|
68
|
-
key). It writes **no** `workflow:` key — the machine is built in — so review and
|
|
69
|
-
commit the file before your first `gtd land`. `gtd init` takes no argument and
|
|
70
|
-
refuses to clobber an existing config; it may also run in a plain parent
|
|
71
|
-
directory (not a git repo) to seed a shared config a nested repo picks up. To
|
|
72
|
-
customize the machine itself, add a `workflow:` key (there is no default
|
|
73
|
-
fallback to merge over — a `workflow:` is the whole definition).
|
|
74
|
-
|
|
75
|
-
gtd requires a repository with **at least one commit** before any state command
|
|
76
|
-
(`land`, `--entry`, `next`, `status`, `abandon`, `restore`, `validate`) will run
|
|
77
|
-
— there is no workflow state to derive from an empty history. Committing the
|
|
78
|
-
`.gtdrc.json` above (or anything else) satisfies this by construction;
|
|
79
|
-
`gtd init`, `gtd install`, `gtd lsp`, `gtd visualize`, and `gtd check` are
|
|
80
|
-
unaffected since none of them derive workflow state.
|
|
81
|
-
|
|
82
|
-
## How it works
|
|
83
|
-
|
|
84
|
-
gtd is a small **pattern machine**: named states, each awaiting one actor and
|
|
85
|
-
carrying one piece of content (a script, a prompt, a message, or a squash commit
|
|
86
|
-
template), with an ordered set of change-patterns routing to the next state.
|
|
87
|
-
|
|
88
|
-
The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
|
|
89
|
-
`"message"` means it's a human's move (stop and hand off, unless it's the run's
|
|
90
|
-
opening beat, which the human triggered by re-running the driver: land that one,
|
|
91
|
-
since changing nothing is itself a declared outcome at some gates); `"capture"`
|
|
92
|
-
means a human gate the human already acted on (land it immediately); `"script"`
|
|
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
|
-
|
|
112
|
-
An `on` edge may also carry a short imperative `action` (e.g. `Accept plan`)
|
|
113
|
-
alongside its existing `describe` sentence — a human-facing name for the choice
|
|
114
|
-
itself, not just the underlying glob pattern. `gtd next` (plain and `--json`)
|
|
115
|
-
and `gtd visualize`'s diagram and inspector all render the two composed
|
|
116
|
-
together: the `action` leads when the edge declares one, with the raw pattern
|
|
117
|
-
(and `describe`) still shown alongside it, so a human choosing between routes
|
|
118
|
-
reads intent first, glob second.
|
|
119
|
-
|
|
120
|
-
**Your input is a spec, not the work.** The unified workflow treats ANY change
|
|
121
|
-
to the tree — a hand-edit to real code, a scratch note in some file, anything at
|
|
122
|
-
all — as a sketch to be finished, never as work to be preserved: there is no
|
|
123
|
-
fork on which steering file you happen to create, and `idle` has exactly one
|
|
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
|
-
|
|
133
|
-
From there, `start-gate.check` runs your test suite on the tree as it now stands
|
|
134
|
-
— which, thanks to the unwind, is simply your repo's own baseline. The rule is
|
|
135
|
-
the plain one: the baseline must be green. A red run halts the process and tells
|
|
136
|
-
you to repair it first (that's what `gtd --entry fix-precheck` is for);
|
|
137
|
-
`review-gate` follows the same rule.
|
|
138
|
-
|
|
139
|
-
Once past the gate, `design.triage` reads the diff that started the process
|
|
140
|
-
itself — the prompt never inlines it; it finds the entry commit (the one
|
|
141
|
-
`unwind` reverted) and runs `git show` against it — and groups that diff into an
|
|
142
|
-
ORDERED list of **concerns**, each one able to leave the test suite green on its
|
|
143
|
-
own, classifying each as **product** (a user-facing/requirements decision) or
|
|
144
|
-
**technical** (an implementation decision). A concern's open point clears a
|
|
145
|
-
three-part bar before it's ever raised as a question: the answer must be a
|
|
146
|
-
genuine fork with divergent outcomes, the sketch/entry diff/history must not
|
|
147
|
-
already settle it, and getting it wrong must survive all the way to human
|
|
148
|
-
review. Anything below that bar — product or technical alike — triage decides
|
|
149
|
-
itself, recording the decision and a one-line rationale under
|
|
150
|
-
`## Answered Questions`, the same section a human-answered question lands in;
|
|
151
|
-
only a product point that clears the bar is raised as an open question in
|
|
152
|
-
`.gtd/REQUIREMENTS.md` (a technical point that clears it waits for the
|
|
153
|
-
architecture phase to raise it), folding EVERYTHING the entry commit added into
|
|
154
|
-
those concerns — a scratch sketch and a hand-edited code change alike, since the
|
|
155
|
-
unwind already reverted both; nothing is left to delete. When every concern
|
|
156
|
-
turns out to be technical, triage writes no `## Open Questions` section at all.
|
|
157
|
-
A shared check+answer gate then probes `.gtd/REQUIREMENTS.md` for unanswered
|
|
158
|
-
questions: each open question offers a couple of candidate answers plus a
|
|
159
|
-
`- [ ] _your answer_` slot, and you tick exactly one per question (the gate
|
|
160
|
-
won't let the phase advance while any question is unanswered) — but a phase
|
|
161
|
-
whose document has no open questions skips that human stop entirely and falls
|
|
162
|
-
straight through. If a question's options ever come out as prose instead of
|
|
163
|
-
`- [ ]` rows, there is no checkbox for the gate to see, so it stays permanently
|
|
164
|
-
unanswered; un-wedge it yourself: hand-write the `- [ ]` rows and tick one,
|
|
165
|
-
delete that question, or delete the whole `## Open Questions` section to accept
|
|
166
|
-
the plan as-is.
|
|
167
|
-
|
|
168
|
-
Once the product questions are settled, `architecture.author` picks up as a
|
|
169
|
-
**cold reader** — a separate machine with its own memory scope, so it does not
|
|
170
|
-
resume the triage conversation but reads `.gtd/REQUIREMENTS.md` in full (cold
|
|
171
|
-
means no memory of that conversation, never no git access — it finds and reads
|
|
172
|
-
the entry commit from history exactly as triage did) — and develops the _how_
|
|
173
|
-
for each settled concern. Because it is the first state that knows the _how_, it
|
|
174
|
-
is also the one state allowed to re-merge concerns whose file footprints
|
|
175
|
-
coincide (never split), recording every merge under a `## Merged Concerns`
|
|
176
|
-
heading. The same three-part bar applies to every technical point here too: only
|
|
177
|
-
one that clears it is raised as an open question in `.gtd/ARCHITECTURE.md`;
|
|
178
|
-
everything below the bar, author decides itself and records under
|
|
179
|
-
`## Answered Questions` right alongside triage's own settled points, all of
|
|
180
|
-
which author treats as SETTLED regardless of whether they're product or
|
|
181
|
-
technical. Open questions are routed through that same shared check+answer gate
|
|
182
|
-
shape, again skipping the human stop when nothing is open, and
|
|
183
|
-
`.gtd/REQUIREMENTS.md` is deleted once its content is folded in. A process where
|
|
184
|
-
nothing ever clears the bar runs both phases straight through with no human stop
|
|
185
|
-
at all — the existing review tail, which sees the whole diff anyway, is the
|
|
186
|
-
veto. Once those are settled, `architecture.decompose` is a purely mechanical
|
|
187
|
-
write-out — **one package file per concern**, in the settled order, with no
|
|
188
|
-
merge/split judgement of its own (triage grouped the concerns, and
|
|
189
|
-
`architecture.author` may already have re-merged some of them by file footprint)
|
|
190
|
-
— handing off to the per-package build queue: each package (a set of independent
|
|
191
|
-
tasks a single build turn fans out to parallel subagents) runs its own test loop
|
|
192
|
-
and a per-package **agentic review** that verifies it against its own spec. A
|
|
193
|
-
package whose work already landed (an earlier package's fix turn pulled it in)
|
|
194
|
-
is not a dead end: the build turn records per-criterion evidence in
|
|
195
|
-
`.gtd/SATISFIED.md` instead of implementing anything, and the package still goes
|
|
196
|
-
through the checks and the spec review before closing out. If a queue item ever
|
|
197
|
-
_does_ dead-end (a build turn that authors nothing stalls), the supported
|
|
198
|
-
recovery is to write that same file yourself and `gtd land` — no hand-authored
|
|
199
|
-
state commit.
|
|
200
|
-
|
|
201
|
-
The process converges on that same shared tail: an agent hands you a
|
|
202
|
-
`.gtd/REVIEW.md` checkbox review of the diff — the prompt never inlines the diff
|
|
203
|
-
itself; it names the commit the changes are based at and the agent runs
|
|
204
|
-
`git diff` to read the range before writing the review. Each hunk is a
|
|
205
|
-
`- [ ] ./path/to/file.ts#42` pointer, and its explanation can either trail the
|
|
206
|
-
pointer on the same line after a dash or sit on the line(s) below it — both
|
|
207
|
-
forms are equally valid, but the same-line form is the reflow-proof one, since a
|
|
208
|
-
continuation line has to be indented exactly two spaces (four or more, after a
|
|
209
|
-
blank line, reads as an indented code block and never reflows):
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
- [ ] ./path/to/file.ts#42 — what this hunk does, with room to run to
|
|
213
|
-
several lines
|
|
214
|
-
- [ ] ./path/to/file.ts#99
|
|
215
|
-
a note on the line below the pointer works too
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
The one rule an explanation must follow either way: it must never itself START
|
|
219
|
-
with a bare `./path` token, since that parses as a second hunk pointer rather
|
|
220
|
-
than a note. If a styled `.gtd/REVIEW.md` ever comes out malformed some other
|
|
221
|
-
way (a paragraph where a `- [ ] ./path#line` row belongs), `gtd check review`
|
|
222
|
-
refuses the step the same way: the file stays exactly as written in the working
|
|
223
|
-
tree, unlanded, and re-running the same prompt is the recovery — nothing is
|
|
224
|
-
lost. While the process rests at that gate, the landing script opens a **review
|
|
225
|
-
checkout window**: HEAD is rewound to the review base with the working tree
|
|
226
|
-
untouched, so the whole reviewable change shows up as ordinary uncommitted
|
|
227
|
-
changes in your editor's normal git integration (and files added during the
|
|
228
|
-
process show up as ordinary untracked files, so discarding one deletes it — an
|
|
229
|
-
untracked file you leave alone is not a pending change, and only actually
|
|
230
|
-
removing it from disk counts as a deletion). The next landing's own script
|
|
231
|
-
closes the window before it commits. Tick a box as you review each hunk (ticking
|
|
232
|
-
just records "I read this"), and leave a **comment** to request changes: a note
|
|
233
|
-
on a line, an inline `// TODO`-style comment in the code, or a direct code edit.
|
|
234
|
-
A comment sends a FULL development lap, not a quick fix-and-re-review: an agent
|
|
235
|
-
first judges whether your comment is actually actionable (a genuinely approving
|
|
236
|
-
remark with no code edit short-circuits straight to sign-off instead), and an
|
|
237
|
-
actionable round is re-planned from scratch through triage, architecture, and
|
|
238
|
-
the package queue again — a hand-edit you made during review is treated as a
|
|
239
|
-
**sketch**, the same as any other change that starts a process, not a fix the
|
|
240
|
-
agent builds on: it is reverted out of the tree first, and your intent survives
|
|
241
|
-
only in your own review-round commit for triage to read. There is no baseline
|
|
242
|
-
check on the way back into planning. Only the turn that drafts the final squash
|
|
243
|
-
message resumes the session that built the feature in the first place (the
|
|
244
|
-
review tail is nested inside that build identity rather than sitting beside it)
|
|
245
|
-
— an actionable round leaves that identity entirely, so it starts a fresh
|
|
246
|
-
session like any other process. A comment is what asks for changes; landing with
|
|
247
|
-
no comment signs off, whatever the boxes say, which collapses the whole process
|
|
248
|
-
into one commit (a **squash finale** whose message an agent drafts). Deleting
|
|
249
|
-
`.gtd/REVIEW.md` is refused — the guard only asks whether the step's diff
|
|
250
|
-
deletes the file.
|
|
251
|
-
|
|
252
|
-
A second entry, the same review tail's own direct entry point —
|
|
253
|
-
`gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
|
|
254
|
-
process reviewing `<commitish>..HEAD` with no build of its own on a clean
|
|
255
|
-
sign-off, e.g. a colleague's PR branch (`review-gate.check`'s `reviewBase:` is a
|
|
256
|
-
template bound to the `reviewBase` var, so supplying it via `--var` fixes the
|
|
257
|
-
whole process's diff base to that commitish) — though an actionable round on
|
|
258
|
-
this entry runs the same full triage → gates → architecture → package-queue lap
|
|
259
|
-
as any other, same as `re-unwind` re-plans a build process's own review
|
|
260
|
-
feedback. A clean sign-off's squash keeps and describes only the fixes made
|
|
261
|
-
_during_ the review (not the reviewed changeset); a clean sign-off with no fixes
|
|
262
|
-
becomes an empty `chore: human review` commit. A third entry,
|
|
263
|
-
`gtd --entry fix-precheck`, starts from a clean `idle` and goes straight into
|
|
264
|
-
repairing a red baseline — repair, review, and squash into one commit. If the
|
|
265
|
-
suite is already green there is nothing to fix, and the log is left untouched —
|
|
266
|
-
no commit is left behind.
|
|
267
|
-
|
|
268
|
-
`--entry` itself isn't limited to states flagged `entry: true` — it accepts
|
|
269
|
-
**any** declared, non-commit state of the active workflow (see
|
|
270
|
-
[`gtd --entry`](#commands) below). `entry: true` only marks a state as an
|
|
271
|
-
_extra_ reachability root (and drives a badge in `gtd visualize`) for a state
|
|
272
|
-
that would otherwise be unreachable from the ordinary `idle` rest —
|
|
273
|
-
`review-gate.check` and `fix-precheck` need it for exactly that reason, while
|
|
274
|
-
`start-gate.check` carries it too (the bundled template dedups its two
|
|
275
|
-
`entryGate` instances — `start-gate` and `review-gate` — into one shared
|
|
276
|
-
machine, so flagging the shared state flags both) even though `idle` already
|
|
277
|
-
reaches it the ordinary way. `entry: true` is not a precondition for `--entry`
|
|
278
|
-
to target a state.
|
|
279
|
-
|
|
280
|
-
Every agent state routes its model through two `vars` tiers — `plannerModel`
|
|
281
|
-
(heavier planning and review) and `coderModel` (the coding turns) — so you can
|
|
282
|
-
repoint the models globally in one place (a `vars:` edit or a `GTD_PLANNERMODEL`
|
|
283
|
-
override) instead of per state. Every steering file the bundled workflow reads
|
|
284
|
-
or writes lives at a fixed path under `.gtd/`; `testCommand`, `plannerModel`,
|
|
285
|
-
`coderModel`, and `reviewBase` are the only vars left to tune.
|
|
286
|
-
|
|
287
|
-
To inspect or change the machine itself, see [Configuration](#configuration) —
|
|
288
|
-
the workflow is just `.gtdrc` config.
|
|
289
|
-
|
|
290
|
-
## Commands
|
|
291
|
-
|
|
292
|
-
```
|
|
293
|
-
Usage: gtd [command] [options]
|
|
294
|
-
|
|
295
|
-
Commands:
|
|
296
|
-
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
297
|
-
default variables you are most likely to change (the test
|
|
298
|
-
command) and a Prettier formatting suggestion. gtd runs its
|
|
299
|
-
built-in workflow by default, so no workflow is written —
|
|
300
|
-
add a workflow: key only to customize the machine itself.
|
|
301
|
-
Takes no argument. Run once per repo; refuses if a gtd
|
|
302
|
-
config already exists. Leaves the file uncommitted for you
|
|
303
|
-
to review and commit
|
|
304
|
-
land Land whatever the tree now shows at the currently resolved
|
|
305
|
-
rest — a human capture, an agent/check turn, an empty
|
|
306
|
-
attempt (a fruitless prompt turn), or a squash. Pass
|
|
307
|
-
--cost=<n> (optionally --model=<name>) to record the
|
|
308
|
-
just-finished invocation's token cost and model on the
|
|
309
|
-
turn commit (summed into it.processCost/
|
|
310
|
-
processCostByModel). Plain (the default) prints ONLY the
|
|
311
|
-
script that records the landing; a driver runs it, e.g.
|
|
312
|
-
`gtd land | sh`. --json/--sh instead emit script (that
|
|
313
|
-
same script, byte-identical) alongside settled, idle,
|
|
314
|
-
state (the post-land target), subject, cost and model —
|
|
315
|
-
--json/--sh are mutually exclusive. Exits 0 on success, 1
|
|
316
|
-
on any refusal — see the Exit codes section below
|
|
317
|
-
(no command) --entry <state>
|
|
318
|
-
Starts a new process authenticated as human, e.g.
|
|
319
|
-
'gtd --entry <state>'
|
|
320
|
-
abandon End the process currently underway without completing it:
|
|
321
|
-
close any open review checkout window, then rewind HEAD to
|
|
322
|
-
the commit the process started from, keeping everything it
|
|
323
|
-
produced as uncommitted changes. A no-op when no process is
|
|
324
|
-
underway
|
|
325
|
-
restore Hard-reset HEAD back to the pre-squash tip retained by the
|
|
326
|
-
last squash/abandon (refs/worktree/gtd/history), undoing a
|
|
327
|
-
squash or bringing back an abandoned process's turns.
|
|
328
|
-
Refuses on a dirty working tree, when there is no retained
|
|
329
|
-
history, or when HEAD has advanced past the squash with
|
|
330
|
-
commits that would be lost
|
|
331
|
-
next Print the resolved rest's beat (no mutation, safe to
|
|
332
|
-
poll), in one of three encodings. Plain (the default): a
|
|
333
|
-
status summary, a blank line, then the step verbatim —
|
|
334
|
-
except at a prompt rest, which is the bare step (plus the
|
|
335
|
-
self-validation instruction when applicable) with no
|
|
336
|
-
header, since those bytes are the agent's own input. --json
|
|
337
|
-
emits the one structured surface gtd has: kind
|
|
338
|
-
(capture|message|script|prompt|stalled) selects what a
|
|
339
|
-
driver does, content is what it runs or shows, idle marks
|
|
340
|
-
the workflow's initial state with a clean tree, plus the
|
|
341
|
-
prompt session, model, validate script, log path, changes,
|
|
342
|
-
next and the resting state's own fields. --sh emits the
|
|
343
|
-
same fields as gtd_-prefixed POSIX shell assignments.
|
|
344
|
-
--json/--sh are mutually exclusive. Exits 0 — see the
|
|
345
|
-
Exit codes section below
|
|
346
|
-
validate Print the script that formats (when declared) then
|
|
347
|
-
validates the resolved rest's steering file, using its
|
|
348
|
-
mode's commands (its file:/mode:), instead of running it —
|
|
349
|
-
a driver runs the script and reads the findings from its
|
|
350
|
-
own exit code/output. Always exits 0; prints "nothing to
|
|
351
|
-
validate" when the resolved rest declares nothing to run.
|
|
352
|
-
On a non-zero validate exit the emitted script prints a
|
|
353
|
-
ready-to-send fix prompt (instruction + findings) and
|
|
354
|
-
exits with the validator's own code
|
|
355
|
-
lsp Start the LSP server for .gtd/ steering files (stdio)
|
|
356
|
-
visualize Serve an interactive diagram of the active workflow on a
|
|
357
|
-
local web server (--port <n>, --no-open). Prints the
|
|
358
|
-
chosen port on its own line — with --port 0, this is the
|
|
359
|
-
only way to learn which port was picked
|
|
360
|
-
check <mode> <file>
|
|
361
|
-
Read <file> and run the built-in steering format named
|
|
362
|
-
<mode> (see `gtd validate`'s modes: qa, review) over its
|
|
363
|
-
contents, printing each finding one per line and exiting
|
|
364
|
-
non-zero when there are any. Resolves no workflow state and
|
|
365
|
-
reads no config — standalone, runnable from any directory
|
|
366
|
-
with <mode>/<file> given explicitly. This is what a
|
|
367
|
-
workflow's emitted validation script invokes as a leaf step.
|
|
368
|
-
--open-questions runs the qa unanswered-questions predicate
|
|
369
|
-
instead (see --help)
|
|
370
|
-
install Print a complete, self-contained briefing that teaches an
|
|
371
|
-
agent (or a human) to build a gtd driver in any shell or
|
|
372
|
-
runtime — the self-serve version of README's 'Writing
|
|
373
|
-
your own driver'. Writes nothing: this installs knowledge
|
|
374
|
-
into the calling agent's context, not files on disk. Runs
|
|
375
|
-
from any directory, in or out of a repository
|
|
376
|
-
version Print version and exit
|
|
377
|
-
help Print this help and exit
|
|
378
|
-
|
|
379
|
-
Options:
|
|
380
|
-
--json (gtd next/gtd land only) output structured JSON
|
|
381
|
-
instead of plain text. Mutually exclusive with --sh
|
|
382
|
-
--sh (gtd next/gtd land only) output gtd_-prefixed POSIX
|
|
383
|
-
shell assignments instead of plain text. Mutually
|
|
384
|
-
exclusive with --json
|
|
385
|
-
--port=<n> (gtd visualize only) port to serve on (default: a free port)
|
|
386
|
-
--no-open (gtd visualize only) do not open the browser
|
|
387
|
-
--cost=<n> (gtd land only) record the invocation's token cost
|
|
388
|
-
--model=<name> (gtd land only, with --cost) tag that cost's model
|
|
389
|
-
--entry <state> (with no command at all) start a brand new process at
|
|
390
|
-
<state> — any declared, non-commit state — authenticated
|
|
391
|
-
as human
|
|
392
|
-
--var <name>=<value>
|
|
393
|
-
(with --entry; repeatable) supply a fixed it.vars
|
|
394
|
-
override for the new process; the name must already be
|
|
395
|
-
declared by the workflow's own vars: or the .gtdrc vars:
|
|
396
|
-
--open-questions (gtd check only) ignore <mode>'s structural findings and
|
|
397
|
-
instead run the qa open-questions predicate over <file>,
|
|
398
|
-
printing each unanswered question one per line and exiting
|
|
399
|
-
non-zero when any remain
|
|
400
|
-
--verbose enable stderr narration for this invocation: which rest
|
|
401
|
-
resolved, which declared pattern each pending change
|
|
402
|
-
matched, which review-window action was emitted, and how
|
|
403
|
-
config resolved across layers. Aliased to -v
|
|
404
|
-
--version, -V Print version and exit
|
|
405
|
-
--help, -h Print this help and exit
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### Plain output is not a parsing surface
|
|
409
|
-
|
|
410
|
-
`gtd next`'s plain encoding is for a human, or a driver that merely displays it
|
|
411
|
-
— never for scraping. Parsing lives in `--json`/`--sh`, both of which read from
|
|
412
|
-
the exact same field set (`gtd install`'s briefing has the full reference);
|
|
413
|
-
anything that greps, cuts, or `awk`s plain text is unsupported, and its shape
|
|
414
|
-
may change across releases with no warning.
|
|
415
|
-
|
|
416
|
-
### Exit codes
|
|
417
|
-
|
|
418
|
-
Closed at five numbers, five meanings — a new command never grows this table;
|
|
419
|
-
whose turn is next lives in `gtd next --json`'s own `kind` field instead.
|
|
420
|
-
|
|
421
|
-
| Code | Meaning |
|
|
422
|
-
| --------- | ---------------- |
|
|
423
|
-
| 0 | success |
|
|
424
|
-
| 1 | runtime error |
|
|
425
|
-
| 2 | usage error |
|
|
426
|
-
| 130 / 143 | SIGINT / SIGTERM |
|
|
427
|
-
|
|
428
|
-
Every command follows this table uniformly, `next`/`land` included: `0` on
|
|
429
|
-
success, `1` on refusal, `2` on usage error, `130`/`143` when gtd itself dies by
|
|
430
|
-
that signal — a parent's `wait` sees a real signal death (`WIFSIGNALED`), not a
|
|
431
|
-
chosen exit code that merely reuses the same number. No command's exit code
|
|
432
|
-
carries a second meaning.
|
|
433
|
-
|
|
434
|
-
**Migration — read this even if you already migrated for a prior release.** This
|
|
435
|
-
is the second inversion in as many releases, folded into one note rather than
|
|
436
|
-
two to compose in your head: `10`/`20` (whose turn was next) are gone — every
|
|
437
|
-
command exits `0`/`1`/`2` uniformly now; `gtd status` is gone (folded into
|
|
438
|
-
`gtd next`); and plain `gtd next` now prints a status header at every kind
|
|
439
|
-
EXCEPT `prompt` — agent input is untouched, since plain `gtd next` at a `prompt`
|
|
440
|
-
rest is byte-identical to before. A driver must read whose turn is next off
|
|
441
|
-
`gtd next --json`'s own `kind` field
|
|
442
|
-
(`capture`/`message`/`script`/`prompt`/`stalled`) — never off gtd's exit code,
|
|
443
|
-
which no longer carries that signal at all.
|
|
444
|
-
|
|
445
|
-
Every usage mistake — an unknown option or command, missing/extra arguments, a
|
|
446
|
-
scope violation (e.g. `--cost` on a command other than `gtd land`), a bad flag
|
|
447
|
-
value — is a USAGE error (`2`), never a runtime error (`1`): nothing was even
|
|
448
|
-
attempted. `--help`/`--version` still exit `0`.
|
|
449
|
-
|
|
450
|
-
`--version` (`-V`) / `gtd version` and `--help` (`-h`) / `gtd help`
|
|
451
|
-
short-circuit before any git or repository-state work — they run outside a repo
|
|
452
|
-
and in any repo state, and print to **stdout** at exit 0. `--verbose` (`-v`) is
|
|
453
|
-
not a short-circuit — it gates narration for whatever command follows (see
|
|
454
|
-
[Narration and remediation](#narration-and-remediation) below). Bare `gtd` (no
|
|
455
|
-
subcommand) is a usage error that exits 2 — gtd decides and prints, full stop;
|
|
456
|
-
driving a loop is a driver's job, not a bundled command (see
|
|
457
|
-
[Driving the loop](#driving-the-loop) below) — printing its help text to
|
|
458
|
-
**stderr**, not stdout: stdout stays byte-empty on every failure, a usage error
|
|
459
|
-
included (see [Error envelope](#error-envelope) below). Any other, truly unknown
|
|
460
|
-
subcommand is likewise a usage error exiting 2 without touching the repository.
|
|
461
|
-
The state commands (`land`, `--entry`, `abandon`, `restore`, `next`, `status`,
|
|
462
|
-
`validate`) must run from the **repository root** — gtd derives the workflow,
|
|
463
|
-
pending changes, and process history relative to cwd, so they refuse with a
|
|
464
|
-
clear error from a subdirectory; `lsp`, `init`, `visualize`, `check`, and
|
|
465
|
-
`install` are standalone and run from anywhere (see each command's own help
|
|
466
|
-
entry).
|
|
467
|
-
|
|
468
|
-
`--json`, `--cost=<n>`, `--model=<name>` (the latter two only for `gtd land`),
|
|
469
|
-
`--entry <state>` (no other command at all), and `--var <name>=<value>` (with
|
|
470
|
-
`--entry`, repeatable) are the only long options the compiled bundle recognizes.
|
|
471
|
-
`--entry`/`--var` accept both the `--flag=value` and the space-separated
|
|
472
|
-
`--flag value` form. Any other `--` option (including a typo like `--jsn`) is
|
|
473
|
-
rejected with a usage error rather than silently ignored, so a mistyped flag can
|
|
474
|
-
never degrade a JSON caller to plain-text mode. `--var` with no `--entry`, a
|
|
475
|
-
duplicate `--var` name, or `--cost`/`--model`/`--entry` combined with another
|
|
476
|
-
command are all usage errors too — landing and entering are different verbs, so
|
|
477
|
-
`gtd land --entry <state>` is a usage error, not a synonym. A bare
|
|
478
|
-
`--cost`/`--model` with no value, a non-numeric or negative `--cost`, an empty
|
|
479
|
-
`--model`, `--model` without `--cost`, or `--cost`/`--model` on any command
|
|
480
|
-
other than `gtd land` are all usage errors.
|
|
481
|
-
|
|
482
|
-
### `gtd next`'s `Next:`/`next`
|
|
483
|
-
|
|
484
|
-
Both plain and `--json` output include a headline preview of what would happen
|
|
485
|
-
next: the first declared `on` edge whose pattern matches the pending changes AS
|
|
486
|
-
A WHOLE (the same first-match-wins semantics `gtd land` itself uses), using its
|
|
487
|
-
`action` when the edge declares one, else its raw pattern, alongside its target
|
|
488
|
-
state. Plain output prints a `Next: <action-or-pattern> → <target>` line (or
|
|
489
|
-
`Next: (no match — nothing would happen)`); `--json`'s `next` key mirrors it as
|
|
490
|
-
`{ action?, pattern, target }`, or `null` on no match.
|
|
491
|
-
|
|
492
|
-
This reports the **declared** route only: a capped `retry` may redirect
|
|
493
|
-
elsewhere when the land is decided, which `Next:`/`next` does not apply — it
|
|
494
|
-
previews what the declared `on` patterns would match, not a guarantee of where a
|
|
495
|
-
real `gtd land` lands.
|
|
496
|
-
|
|
497
|
-
### Error envelope
|
|
498
|
-
|
|
499
|
-
**stdout is either the complete artifact or byte-empty — never a partial write
|
|
500
|
-
followed by an error.** Every command buffers everything it would print and
|
|
501
|
-
flushes that buffer to stdout exactly once, only after it succeeds; on any
|
|
502
|
-
failure the buffer is simply discarded, so stdout never carries a half-written
|
|
503
|
-
prompt/script alongside a message about why it stopped. `gtd visualize` is the
|
|
504
|
-
one exception worth knowing: it flushes its served-URL line immediately, before
|
|
505
|
-
blocking on `Ctrl-C`, since a flush-on-success would never otherwise fire.
|
|
506
|
-
|
|
507
|
-
Any invocation that carries `--json` (valid only for `gtd next`/`gtd land` —
|
|
508
|
-
every other command usage-errors on it) reports a failure as a machine-readable
|
|
509
|
-
envelope on **stderr** — including the scope violation itself, so a driver that
|
|
510
|
-
always adds `--json` still gets a parseable envelope on every failure, not only
|
|
511
|
-
`gtd next`'s/`gtd land`'s own:
|
|
512
|
-
|
|
513
|
-
```json
|
|
514
|
-
{ "state": "error", "prompt": "<message>" }
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
This covers every failure mode, not just a command's own refusal (exit 1): a
|
|
518
|
-
**usage error** (an unknown flag, a missing argument, `gtd --entry version`'s
|
|
519
|
-
"not an enterable state" — exit 2) and a **defect** (a layer throwing outside
|
|
520
|
-
the ordinary error channel — exit 1) both get the same envelope shape — there is
|
|
521
|
-
no failure path that reaches `--json` without one — but a usage error's exit
|
|
522
|
-
code is 2, never 1, so a driver can tell "you invoked gtd wrong" apart from "gtd
|
|
523
|
-
refused/broke" (see [Exit codes](#exit-codes)).
|
|
524
|
-
|
|
525
|
-
A human-readable `gtd: <message>` line is also always written to **stderr**,
|
|
526
|
-
right after the envelope — stdout carries neither one on a failing run. Stderr
|
|
527
|
-
always carries exactly one `gtd: ` prefix: a message already authored with its
|
|
528
|
-
own `gtd:`/`gtd <cmd>:` prefix is never doubled.
|
|
529
|
-
|
|
530
|
-
**Accepted cost:** a driver that pipes stdout into `jq` on a failed run now
|
|
531
|
-
reads nothing instead of a parseable error object — it must read stderr or the
|
|
532
|
-
exit code to learn why a run failed.
|
|
533
|
-
|
|
534
|
-
### Narration and remediation
|
|
535
|
-
|
|
536
|
-
Stderr carries two things beyond the `gtd: ` message line above: NARRATION,
|
|
537
|
-
gated by `--verbose`/`-v`, and REMEDIATION, unconditional.
|
|
538
|
-
|
|
539
|
-
`--verbose` (alias `-v`) turns on one line of commentary per in-process fact a
|
|
540
|
-
command's dispatch already computes — which rest resolved, which declared
|
|
541
|
-
pattern each pending change matched, which review-window action was emitted, and
|
|
542
|
-
how config resolved across `.gtdrc` layers. Without it, none of this is printed;
|
|
543
|
-
stdout is never touched either way — narration is a stderr-only concern, exactly
|
|
544
|
-
like the error envelope above.
|
|
545
|
-
|
|
546
|
-
A failure's remediation detail is unconditional — it prints at every verbosity,
|
|
547
|
-
on the line(s) right after the `gtd: `-prefixed message, each indented two
|
|
548
|
-
spaces: a bad config key names the offending key and which `.gtdrc` layer it
|
|
549
|
-
came from, a corrupted ref names the ref, and a missing binary in a steering
|
|
550
|
-
mode's `format:`/`validate:` command names the resolved `$PATH` it was looked up
|
|
551
|
-
in.
|
|
552
|
-
|
|
553
|
-
### Non-interactive today
|
|
554
|
-
|
|
555
|
-
gtd is non-interactive: no readline, no `/dev/tty`, no prompt call anywhere in
|
|
556
|
-
`src/`. If interaction is ever added, it goes to `/dev/tty`, never stderr, and
|
|
557
|
-
never blocks — a question fails with a code when the tty cannot be opened rather
|
|
558
|
-
than hanging. The reason is the section above: stderr already has two occupants,
|
|
559
|
-
narration and remediation, and a question mixed into that stream would deadlock
|
|
560
|
-
a driver that never reads stderr.
|
|
561
|
-
|
|
562
|
-
## Driving the loop
|
|
563
|
-
|
|
564
|
-
gtd itself is not a loop — it decides and prints, full stop; driving a loop is a
|
|
565
|
-
driver's job. The README's minimal driver below (see
|
|
566
|
-
[A complete minimal driver](#a-complete-minimal-driver)) is that driver: save it
|
|
567
|
-
as `~/.local/bin/gtd-loop`, `chmod +x` it, and run it from a repository root —
|
|
568
|
-
it takes no arguments and runs the loop until it's your turn. It drives the
|
|
569
|
-
autonomous states (agent turns, check runs) and stops at the first
|
|
570
|
-
non-autonomous one: reaching a human gate it prints the gate's message and
|
|
571
|
-
exits. You act by editing files (answer a plan question, tick a review box, fix
|
|
572
|
-
code) and re-running it — your pending edit arrives as the loop's first beat
|
|
573
|
-
(`kind: "capture"`, landed immediately), so you never run `gtd land` by hand.
|
|
574
|
-
|
|
575
|
-
Some gates could accept by INACTION instead: a workflow may declare a `"C"`
|
|
576
|
-
(clean-tree) pattern on a human-actor state that routes onward, so changing
|
|
577
|
-
nothing there means "accept as-is" rather than nothing happening. That reaches
|
|
578
|
-
the driver as `kind: "message"` either way, which no inspection can tell apart
|
|
579
|
-
from a gate you have not read yet — so the driver treats its OPENING beat as
|
|
580
|
-
yours regardless and lands it: you re-ran it while resting there, and that
|
|
581
|
-
invocation IS the decision. Every later beat halts as usual, because a gate the
|
|
582
|
-
driver produced mid-run is one you have not seen. Landing an opening beat at a
|
|
583
|
-
gate with no `"C"` pattern is harmless — a benign no-op (see `gtd land`'s exit
|
|
584
|
-
codes below) — and the gate then prints on the next beat. A mid-process restart
|
|
585
|
-
simply resumes driving from whatever beat is actually next.
|
|
586
|
-
|
|
587
|
-
Anything richer at that boundary — opening your editor, desktop notifications,
|
|
588
|
-
terminal-multiplexer status — is the job of an outer wrapper around the driver,
|
|
589
|
-
not of the loop itself; see
|
|
590
|
-
[Terminal-multiplexer status: a herdr wrapper](#terminal-multiplexer-status-a-herdr-wrapper)
|
|
591
|
-
below for a worked example.
|
|
592
|
-
|
|
593
|
-
A workflow state can declare an optional `label:` — a human-readable display
|
|
594
|
-
name surfaced in `gtd next --json`/plain `gtd next`. The driver uses it for its
|
|
595
|
-
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
596
|
-
can use it the same way.
|
|
597
|
-
|
|
598
|
-
A machine may also declare `system:` — like `model:`, stamped once at the
|
|
599
|
-
machine level onto every one of that machine's own `prompt` states, never
|
|
600
|
-
authored on an individual state. It is passed to the agent CLI as (in the
|
|
601
|
-
reference driver's case) `--system-prompt`, which **replaces** the harness's own
|
|
602
|
-
default system prompt outright rather than appending to it (contrast with an
|
|
603
|
-
`--append-system-prompt`-shaped flag) — so a machine declaring `system:` loses
|
|
604
|
-
not only the harness's own tool-use instructions but also its dynamic per-turn
|
|
605
|
-
sections: current working directory, environment info, memory-path information,
|
|
606
|
-
git status. A workflow author reaching for `system:` for the first time is
|
|
607
|
-
therefore writing a complete replacement system prompt, not a tweak on top of
|
|
608
|
-
the harness's own. The scoping is machine-level for the same reason memory is
|
|
609
|
-
machine-scoped below: a machine is the unit of conversational identity, so its
|
|
610
|
-
system prompt — like its model tier — is one property of that identity, constant
|
|
611
|
-
across every turn of the one resumed session, never a per-state override. Like
|
|
612
|
-
`model:`/`file:`, a `system:` value prefixed `./` or `../` is inlined from the
|
|
613
|
-
declaring config file's own directory at compile time — for a user's own
|
|
614
|
-
`.gtdrc` `workflow:` config only; the bundled template ships no filesystem
|
|
615
|
-
references at all, since it has to work inside a single-file build. The bundled
|
|
616
|
-
default template ships six such personas already, one per prompt-bearing machine
|
|
617
|
-
— `designPersona`, `architectPersona`, `reviewerPersona`, `specReviewerPersona`,
|
|
618
|
-
`builderPersona`, `finisherPersona` — and, like any other bundled var, each is
|
|
619
|
-
overridable through a top-level `.gtdrc` `vars:` key or a `GTD_<NAME>`-style
|
|
620
|
-
environment variable (e.g. `GTD_DESIGNPERSONA`) — gtd's existing, generic
|
|
621
|
-
vars-override mechanism, nothing new.
|
|
622
|
-
|
|
623
|
-
Memory is **entry-scoped to a machine**, not a state-authored label: each
|
|
624
|
-
machine instance (a node in the `machines:` tree, e.g. `build`, `build.health`,
|
|
625
|
-
`packages.item`, `packages.item.health`) owns its own conversational scope, and
|
|
626
|
-
a `prompt`-content state's `memory` key — surfaced in `gtd next --json`'s
|
|
627
|
-
`memory` field, and as a `Memory: <key>` line in plain `gtd next` — is computed,
|
|
628
|
-
never authored, as `<scope>#<hash7>`: `<scope>` is that machine instance's
|
|
629
|
-
dotted path (the root instance is shown as `root`), and `<hash7>` anchors to the
|
|
630
|
-
commit the CURRENT unbroken entry into that scope started FROM. Entering a
|
|
631
|
-
**descendant** scope (e.g. dipping from `build` into `build.health`) does not
|
|
632
|
-
break the parent's unbroken run — a full agent turn in a nested child machine,
|
|
633
|
-
then back to the parent, still resumes the SAME parent conversation; entering a
|
|
634
|
-
**sibling or unrelated** scope does start a fresh one. The bundled template's
|
|
635
|
-
`build.review` (the human review tail) is a worked example: it is nested INSIDE
|
|
636
|
-
`build` (the builder's own machine) so that a `gtd --entry fix-precheck` run —
|
|
637
|
-
`build.fix` -> `build.health.check` -> `build.review.*` -> `build.squashing` —
|
|
638
|
-
stays inside one subtree, letting `build.squashing` resume the SAME session that
|
|
639
|
-
made the fixes instead of a root-level sibling forcing it to re-derive the range
|
|
640
|
-
from scratch on every pass through the tail. (An actionable review round breaks
|
|
641
|
-
the run on purpose instead — it leaves `build` entirely through a root-level
|
|
642
|
-
`re-unwind` state and a full re-plan, since a hand-edit made during review is a
|
|
643
|
-
sketch to reconsider, not a fix to build on.) Two instances of the same reusable
|
|
644
|
-
machine (e.g. `build.health` and `packages.item.health`, both instantiating
|
|
645
|
-
`healthGate`) get different scopes and so never share a key, even though they're
|
|
646
|
-
the "same shaped" machine. One consequence is a structural guarantee: **a
|
|
647
|
-
reviewer's turn never resumes an implementer's session, and vice versa** — a
|
|
648
|
-
reviewer machine and the implementer machine it reviews are always different
|
|
649
|
-
instances with different scopes.
|
|
650
|
-
|
|
651
|
-
`gtd` itself stores NOTHING to make this work: `session.id` is
|
|
652
|
-
`UUIDv5(<fixed gtd namespace>, <memory key>)` — a deterministic hash of the
|
|
653
|
-
computed `<scope>#<hash7>` key above — so the same scope-run always re-derives
|
|
654
|
-
the exact same id, and there is no table, no file, no write to keep in sync.
|
|
655
|
-
`session.resume` is `true` iff a prior `prompt` turn already landed within that
|
|
656
|
-
same scope-run. Because nothing is written, calling `gtd next --json` twice in a
|
|
657
|
-
row — a driver's own opening peek, a status poll, a curious human — derives
|
|
658
|
-
IDENTICAL `session.id`/`session.resume` values both times, since there is
|
|
659
|
-
nothing to poison: looking is free, and there is no separate claiming form to
|
|
660
|
-
protect. The per-scope survival story (a child machine's own excursion doesn't
|
|
661
|
-
disturb the parent's session) falls out of the key itself, not out of a
|
|
662
|
-
per-scope row: the parent's anchor commit is unaffected by whatever the child
|
|
663
|
-
scope does in between. A driver maps `session.id`/`session.resume` straight onto
|
|
664
|
-
the agent CLI's own session flags — the README's minimal driver passes them as
|
|
665
|
-
`--session-id <id>`/`--resume <id>` to `claude -p`, but treats `resume` as a
|
|
666
|
-
HINT rather than a contract: it tries the flag `resume` points at first and
|
|
667
|
-
falls back to the other on failure. That fallback is what recovers BOTH
|
|
668
|
-
mismatches that used to need manual intervention — a crashed prior turn that
|
|
669
|
-
minted an id but landed no commit (so the next lap re-derives the SAME id with
|
|
670
|
-
`resume: false`, and `--session-id` on an already-used id fails, so the driver
|
|
671
|
-
falls back to `--resume`), and the inverse (`resume: true`, but the remembered
|
|
672
|
-
session is gone — retention expired, `~/.claude/projects` wiped — so `--resume`
|
|
673
|
-
fails and the driver falls back to `--session-id`). There is no file to delete
|
|
674
|
-
and nothing to restart.
|
|
675
|
-
|
|
676
|
-
### Terminal-multiplexer status: a herdr wrapper
|
|
677
|
-
|
|
678
|
-
[herdr](https://herdr.dev) shows a per-pane status in its sidebar. This wrapper
|
|
679
|
-
reports `gtd`'s own lifecycle to that status without any herdr-specific
|
|
680
|
-
knowledge in `gtd` itself: `working` while the loop drives, `blocked` when it
|
|
681
|
-
comes to rest on a human (a gate, a stall, a non-zero exit), and `idle`
|
|
682
|
-
(rendered as "done" once the pane's tab is unfocused) when a run ends without
|
|
683
|
-
anything owed. It needs nothing from `gtd` beyond `gtd next --sh`'s `gtd_actor`
|
|
684
|
-
field — a strictly read-only orientation peek.
|
|
685
|
-
|
|
686
|
-
Save this as `~/.local/bin/gtdh`, `chmod +x` it, and run `gtdh` in place of
|
|
687
|
-
`gtd-loop` — it's a plain bash file, so it works from fish or any other shell.
|
|
688
|
-
|
|
689
|
-
```bash
|
|
690
|
-
#!/usr/bin/env bash
|
|
691
|
-
# Report gtd's own lifecycle to the herdr pane it runs in: working while the
|
|
692
|
-
# loop drives, blocked when it rests on you, idle (shown as "done") otherwise.
|
|
693
|
-
# Needs nothing from gtd but `gtd next --sh`. Outside herdr it is a plain
|
|
694
|
-
# passthrough.
|
|
695
|
-
set -uo pipefail
|
|
696
|
-
|
|
697
|
-
pane="${HERDR_PANE_ID:-}"
|
|
698
|
-
|
|
699
|
-
# `--agent gtd` is deliberate: herdr only lets screen detection (or a visible
|
|
700
|
-
# approval prompt) override a reported state when the reported label names a
|
|
701
|
-
# KNOWN agent. "gtd" names none, so the loop's `claude -p` turns can never move
|
|
702
|
-
# this pane's status.
|
|
703
|
-
report() { # report <state>
|
|
704
|
-
[ -n "$pane" ] || return 0
|
|
705
|
-
herdr pane report-agent "$pane" \
|
|
706
|
-
--source custom:gtd --agent gtd --state "$1" >/dev/null 2>&1 || true
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
report working
|
|
710
|
-
trap 'report blocked; exit 130' INT TERM
|
|
711
|
-
|
|
712
|
-
# HERDR_PANE_ID is unset for the loop: herdr's Claude Code hook needs it, and
|
|
713
|
-
# without it no `claude -p` turn reports a claude SESSION identity for this
|
|
714
|
-
# pane. That is load-bearing, not cosmetic — a pane that owns one rejects every
|
|
715
|
-
# later state report, so the report below would be dropped.
|
|
716
|
-
env -u HERDR_PANE_ID gtd-loop
|
|
717
|
-
rc=$?
|
|
718
|
-
|
|
719
|
-
# Whose turn is it now? `gtd next --sh` is a strictly read-only peek — every
|
|
720
|
-
# gtd command is, including `gtd next` (its prompt session id is derived,
|
|
721
|
-
# never minted/stored) — only the emitted scripts a driver runs actually
|
|
722
|
-
# touch git. A human actor means gtd is waiting on you; anything else means
|
|
723
|
-
# the run ended with nothing owed. Assign first, THEN eval — same reason as
|
|
724
|
-
# the driver above.
|
|
725
|
-
out="$(gtd next --sh 2>/dev/null)" || out=""
|
|
726
|
-
eval "${out:-}" 2>/dev/null || true
|
|
727
|
-
|
|
728
|
-
if [ "$rc" -ne 0 ] || [ -z "${gtd_actor:-}" ] || [ "$gtd_actor" = human ]; then
|
|
729
|
-
report blocked
|
|
730
|
-
else
|
|
731
|
-
report idle
|
|
732
|
-
fi
|
|
733
|
-
|
|
734
|
-
exit $rc
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
A few things to know before relying on it:
|
|
738
|
-
|
|
739
|
-
- **Give `gtd` its own pane.** Run an interactive `claude` (or any agent whose
|
|
740
|
-
herdr integration reports session identity) in the same pane and that pane
|
|
741
|
-
owns a `herdr:claude` session until the process exits; while it does, the
|
|
742
|
-
wrapper's reports are silently ignored and the sidebar stops tracking `gtd`.
|
|
743
|
-
- **`blocked` covers the resting `idle` state too** — gtd's own `idle` is a
|
|
744
|
-
human gate (it waits for you to write a steering file), so a finished process
|
|
745
|
-
reads as "your turn", which is what it is.
|
|
746
|
-
- The status **persists after the wrapper exits** — that's the point: the
|
|
747
|
-
sidebar keeps showing which worktree is waiting on you. Hand the pane back to
|
|
748
|
-
ordinary detection with
|
|
749
|
-
`herdr pane release-agent "$HERDR_PANE_ID" --source custom:gtd --agent gtd`.
|
|
750
|
-
- A non-zero exit (a stall, a refusal, an error) reports `blocked`: herdr has no
|
|
751
|
-
failure state, and those all need a human.
|
|
752
|
-
- Ctrl-C reports `blocked` rather than leaving a stale `working`.
|
|
753
|
-
|
|
754
|
-
Optionally,
|
|
755
|
-
`herdr pane report-metadata "$HERDR_PANE_ID" --source custom:gtd-display --token summary=<text>`
|
|
756
|
-
sets a value renderable as `$summary` in an Agent sidebar row, if you want more
|
|
757
|
-
than the state itself.
|
|
758
|
-
|
|
759
|
-
## Writing your own driver
|
|
760
|
-
|
|
761
|
-
Run `gtd install` to print this whole chapter's protocol as a complete,
|
|
762
|
-
self-contained briefing — paste it into an agent's context (or read it yourself)
|
|
763
|
-
to build a driver without leaving your terminal. The briefing ends by directing
|
|
764
|
-
the agent to INTERVIEW you first — which agent CLI runs the turns, under what
|
|
765
|
-
permission model, how you want to invoke the loop, what should happen at a human
|
|
766
|
-
gate — and to build the driver your answers describe rather than copying the
|
|
767
|
-
reference sh. It writes nothing: "install" means installing knowledge into the
|
|
768
|
-
calling agent's context, and the briefing it prints is always exactly as current
|
|
769
|
-
as the `gtd` that printed it.
|
|
770
|
-
|
|
771
|
-
The README's minimal driver below is gtd's own reference driver, not a
|
|
772
|
-
privileged one — the engine itself is a supported public surface, and anything
|
|
773
|
-
below holds for any driver you write against it. gtd decides and prints; it
|
|
774
|
-
never touches git itself. The four commands that change anything — `gtd land`,
|
|
775
|
-
`gtd --entry <state>`, `gtd abandon`, and `gtd restore` — perform no git write
|
|
776
|
-
when run: each one prints ONE POSIX sh script for YOU to execute
|
|
777
|
-
(`src/Emit.ts`'s `combinedScript`) — a leading comment ("gtd emitted this and
|
|
778
|
-
did NOT run it — pipe it into `sh` to land the turn"), then the REQUIRED half
|
|
779
|
-
verbatim, then, only when there's a presentation-only follow-up, a second
|
|
780
|
-
comment ("presentation only — safe to skip") and the OPTIONAL half wrapped in a
|
|
781
|
-
subshell whose own non-zero exit is swallowed (reported to stderr as a warning,
|
|
782
|
-
never turning a landed turn into a failing one). Printing gtd's output and never
|
|
783
|
-
running it is not driving anything; a driver must pipe or execute what gtd
|
|
784
|
-
prints — e.g. the capture-then-pipe form the reference driver below uses — never
|
|
785
|
-
a bare `gtd land | sh`, which would hand an empty script to a shell on a refusal
|
|
786
|
-
instead of stopping first (see the reference driver's own use of `gtd land --sh`
|
|
787
|
-
below).
|
|
788
|
-
|
|
789
|
-
Every script gtd emits — `gtd land`, `gtd --entry <state>`, `gtd abandon`,
|
|
790
|
-
`gtd restore`, and the format/validate script `gtd validate` prints — is POSIX
|
|
791
|
-
`sh`, portable to `dash`: a driver may run any of them with any POSIX-compliant
|
|
792
|
-
shell, not specifically bash. The same convention extends to the workflow's own
|
|
793
|
-
`vars.testCommand` (what a `script`-content check state actually executes): it
|
|
794
|
-
is expected to be POSIX sh-compatible too, but this is a DOCUMENTED CONVENTION
|
|
795
|
-
only — gtd never inspects or validates `testCommand`'s shell dialect itself, it
|
|
796
|
-
only renders the value into a script and hands it to whatever shell the driver
|
|
797
|
-
invokes that script with.
|
|
798
|
-
|
|
799
|
-
- **The required half** is everything that decides what lands in git — closing
|
|
800
|
-
an open review checkout window, the resting state's own steering-mode
|
|
801
|
-
`format:`/`validate:` commands, the commit or squash itself (`gtd land` and
|
|
802
|
-
`gtd --entry <state>`), or the ref update and reset that undo a process
|
|
803
|
-
(`gtd abandon`, `gtd restore`) — and, last, a printed line naming what just
|
|
804
|
-
landed (`src/OutcomeScript.ts`'s `gtd_report_*` calls): a transition or
|
|
805
|
-
capture's changed-file rows, or the abandon/restore prose, resolved from the
|
|
806
|
-
repository AFTER the write above it. Its own exit code IS the printed script's
|
|
807
|
-
exit code — skipping it means the turn never lands, and you never see what it
|
|
808
|
-
did.
|
|
809
|
-
- **The optional half** is presentation only: re-opening the review checkout
|
|
810
|
-
window (the `<<<<<<< HEAD` diff view) after `gtd land` lands at a
|
|
811
|
-
`reviewWindow: true` state, so an editor's diff view has something to show.
|
|
812
|
-
Its own failure is swallowed by the wrapping subshell (a warning on stderr,
|
|
813
|
-
nothing more) — skip it (or let it fail) and you lose nothing but that view,
|
|
814
|
-
the workflow is still driven correctly. `gtd abandon`/`gtd restore` never
|
|
815
|
-
carry one — there is no window to reopen after either, so their printed
|
|
816
|
-
artifact is the required half alone, with no trailing comment/subshell at all.
|
|
817
|
-
|
|
818
|
-
`gtd next --json` carries one more field worth a custom driver's attention:
|
|
819
|
-
|
|
820
|
-
- **`log`** — the per-worktree loop log path, always present. It's derived from
|
|
821
|
-
the worktree's own git dir, so two worktrees looping concurrently never share
|
|
822
|
-
one file; set `$GTD_LOOP_LOG` to override it verbatim, or `$GIT_DIR` to name
|
|
823
|
-
the git dir directly (both honored the same way every git subprocess already
|
|
824
|
-
honors them — an inherited `$GIT_DIR` moves the log path along with it). gtd
|
|
825
|
-
itself neither creates nor truncates this file — a driver appends subprocess
|
|
826
|
-
output to it and truncates once at the start of a run, exactly like the driver
|
|
827
|
-
above does.
|
|
828
|
-
|
|
829
|
-
Even a genuine no-op `gtd land` (a clean tree matching no `on` pattern) prints a
|
|
830
|
-
PRINT-ONLY script: no git write, just the same `nothing to do at "<state>"` line
|
|
831
|
-
the script prints when a driver runs it. Every encoding (plain, `--json`,
|
|
832
|
-
`--sh`) carries that same script verbatim in its `script` field — running it is
|
|
833
|
-
never optional, whichever encoding a driver reads it from. The script is
|
|
834
|
-
self-contained (it carries its own precondition assert and retry helper) and
|
|
835
|
-
safe to run standalone, in sequence, or not at all — paste it into a terminal
|
|
836
|
-
and it does exactly what it says, printed output included: it detects its own
|
|
837
|
-
tty/`NO_COLOR` at RUN time (not at the moment gtd generated it), so the
|
|
838
|
-
fancy/plain rendering always matches wherever you actually run it.
|
|
839
|
-
|
|
840
|
-
A driver has no opening move. It does not need to know whose turn it is before
|
|
841
|
-
it starts — `gtd next --json`/`--sh` tells it, and a human's pending edit
|
|
842
|
-
arrives as an ordinary `kind: "capture"` beat that the driver lands like any
|
|
843
|
-
other. The rule this replaces: never `gtd land` outside a beat you dispatched. A
|
|
844
|
-
stray land at a clean `prompt` rest authors an empty attempt on purpose (that IS
|
|
845
|
-
the stall bookkeeping), so an unconditional opening land would manufacture a
|
|
846
|
-
stall out of a fresh start. The one EXCEPTION is the run's very first beat: land
|
|
847
|
-
it before trusting `idle` there too, so a workflow whose initial state declares
|
|
848
|
-
its own clean-tree `"C"` pattern still gets a chance to advance on a human's
|
|
849
|
-
bare re-invocation, rather than the driver concluding "nothing owed" without
|
|
850
|
-
ever giving that edge a turn (see the reference driver's own comment on this).
|
|
851
61
|
|
|
852
|
-
|
|
853
|
-
exits `0` on success (see [Exit codes](#exit-codes) above). Whose turn is next,
|
|
854
|
-
and whether a landing settles, are both PLAIN FIELDS on `gtd next --json`/`--sh`
|
|
855
|
-
and `gtd land --json`/`--sh`: `next`'s own `kind`
|
|
856
|
-
(`capture`/`message`/`script`/`prompt`/`stalled`) and `idle`; `land`'s own
|
|
857
|
-
`settled` and `idle`. `gtd land --sh` carries `settled`/`idle` alongside the
|
|
858
|
-
script itself, so ONE invocation both runs the landing and tells the driver
|
|
859
|
-
whether to stop — no second read needed to decide. A no-op at a `script` rest
|
|
860
|
-
settles right where it rests (stop immediately, nothing more to read); an
|
|
861
|
-
ordinary or squash landing that finishes the whole process instead resolves
|
|
862
|
-
`idle` on the FOLLOWING `gtd next` — the reference driver below reads once more
|
|
863
|
-
only to DISPLAY that gate's message, never to decide whether to stop. Declaring
|
|
864
|
-
a `C` edge on a `script` state is the workflow-side way to make the state
|
|
865
|
-
advance instead of settling.
|
|
866
|
-
|
|
867
|
-
### Drivers other than sh
|
|
868
|
-
|
|
869
|
-
The protocol above is JSON in (from `gtd next --json` alone), subprocesses out —
|
|
870
|
-
sh is one convenient shape, not the only one. A **program** parses
|
|
871
|
-
`gtd next --json`, switches on `kind`, and runs `gtd next`'s (plain-text) output
|
|
872
|
-
and `gtd land`'s (POSIX sh) output as subprocesses — no sh anywhere in it. A
|
|
873
|
-
**human** runs plain `gtd next` (there is no JSON form of it at all), does what
|
|
874
|
-
it says, and pastes `gtd land | sh`: plain-text output exists for exactly this,
|
|
875
|
-
printing the combined script ready to paste. An **agent** gets `gtd install` as
|
|
876
|
-
its instructions — the same protocol, in a form built to be pasted into a
|
|
877
|
-
context window rather than read. A **CI job** is the program case with the
|
|
878
|
-
`prompt` arm pointed at a headless agent CLI, and
|
|
879
|
-
`kind: "message"`/`kind: "stalled"` mapped onto "stop and report".
|
|
880
|
-
|
|
881
|
-
### A complete minimal driver
|
|
882
|
-
|
|
883
|
-
This is the whole protocol described above, compressed into a loop small enough
|
|
884
|
-
to paste from this README and own outright — save it as `~/.local/bin/gtd-loop`
|
|
885
|
-
(`chmod +x`) and it's the ready-to-run driver
|
|
886
|
-
[Driving the loop](#driving-the-loop) above points at; swap the `claude` line
|
|
887
|
-
for any agent CLI and it keeps working.
|
|
888
|
-
|
|
889
|
-
This exact fenced block is extracted and executed by
|
|
890
|
-
`tests/integration/features/readme-driver.feature` — renaming the heading above
|
|
891
|
-
or splitting the block into more than one fence breaks the build.
|
|
892
|
-
|
|
893
|
-
**Pipe the prompt to the agent CLI on stdin — never pass it as a command-line
|
|
894
|
-
argument.** A `prompt` beat's content embeds a full diff, and argv is capped:
|
|
895
|
-
roughly 1 MB on macOS, and POSIX guarantees only 4 KB (`ARG_MAX`). Both are
|
|
896
|
-
reachable by an ordinary diff, so a driver that passes the prompt as an argument
|
|
897
|
-
works in testing and then fails on the first large change, in a way that looks
|
|
898
|
-
like an agent error rather than a driver bug. The driver below pipes `$c`/`$out`
|
|
899
|
-
into `claude -p` over stdin for exactly this reason.
|
|
62
|
+
The loop behind all of this is one beat, repeated:
|
|
900
63
|
|
|
901
64
|
```bash
|
|
902
|
-
#!/usr/bin/env sh
|
|
903
|
-
set -eu
|
|
904
|
-
|
|
905
|
-
beat=1
|
|
906
65
|
while :; do
|
|
907
|
-
# One invocation per beat: assign first, THEN eval — command substitution
|
|
908
|
-
# inside eval's own argument would swallow a failed `gtd next` under
|
|
909
|
-
# `set -e` (eval would see only the empty string and abort on some later
|
|
910
|
-
# unset variable with a confusing message). Assigning to `out` first makes
|
|
911
|
-
# this a simple command whose own exit status IS the substitution's, so
|
|
912
|
-
# `set -e` aborts correctly on a genuine failure.
|
|
913
66
|
out="$(gtd next --sh)"
|
|
914
67
|
eval "$out"
|
|
915
|
-
|
|
916
|
-
# `gtd_idle` (true iff the initial state, clean tree) is the one shape
|
|
917
|
-
# that means the process is genuinely done — EXCEPT on the run's opening
|
|
918
|
-
# beat: land it anyway, so a workflow whose initial state declares its own
|
|
919
|
-
# clean-tree "C" pattern still gets a chance to fire.
|
|
920
|
-
if [ "$beat" -gt 1 ] && [ "${gtd_idle:-}" = true ]; then
|
|
921
|
-
gtd next
|
|
922
|
-
exit 0
|
|
923
|
-
fi
|
|
924
|
-
|
|
925
68
|
case "$gtd_kind" in
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
# editing nothing, so land the opening beat either way. Later beats are
|
|
929
|
-
# gates we just produced and you have not read yet — hand off.
|
|
930
|
-
message) [ "$beat" = 1 ] || { gtd next; exit 0; } ;;
|
|
931
|
-
capture) ;; # the human already acted — just land it
|
|
932
|
-
script)
|
|
933
|
-
sh -c "$gtd_content" >>"$gtd_log" 2>&1 || true ;;
|
|
934
|
-
prompt)
|
|
935
|
-
# $gtd_content embeds a full diff, so it goes to the agent over
|
|
936
|
-
# stdin, never as an argv positional — argv is capped (~1 MB on
|
|
937
|
-
# macOS, and POSIX guarantees only 4 KB, ARG_MAX), and a diff crosses
|
|
938
|
-
# that far sooner than you'd expect.
|
|
939
|
-
agent_turn() { printf '%s' "$gtd_content" | claude -p "$1" "$gtd_session_id" \
|
|
940
|
-
${gtd_model:+--model "$gtd_model"} \
|
|
941
|
-
${gtd_system:+--system-prompt "$gtd_system"} \
|
|
942
|
-
--dangerously-skip-permissions >>"$gtd_log" 2>&1; }
|
|
943
|
-
if [ "${gtd_session_resume:-}" = true ]
|
|
944
|
-
then agent_turn --resume || agent_turn --session-id
|
|
945
|
-
else agent_turn --session-id || agent_turn --resume
|
|
946
|
-
fi
|
|
947
|
-
n=0
|
|
948
|
-
while [ -n "${gtd_validate:-}" ] && ! fix="$(sh -c "$gtd_validate" 2>&1)"; do
|
|
949
|
-
n=$((n + 1)) && [ "$n" -gt 3 ] && { printf '%s\n' "$fix" >&2; exit 1; }
|
|
950
|
-
# $fix IS the fix prompt, verbatim — piped for the same reason as
|
|
951
|
-
# $gtd_content above. Whether `claude --resume` re-applies the
|
|
952
|
-
# original session's model/system prompt is a harness detail gtd
|
|
953
|
-
# cannot verify from outside, so this passes the identical
|
|
954
|
-
# $gtd_model/$gtd_system on both calls rather than assume it does —
|
|
955
|
-
# otherwise this fix turn might silently fall back to Claude Code's
|
|
956
|
-
# own defaults while the turn that produced the file ran under the
|
|
957
|
-
# workflow's own model and persona.
|
|
958
|
-
printf '%s' "$fix" | claude -p --resume "$gtd_session_id" \
|
|
959
|
-
${gtd_model:+--model "$gtd_model"} \
|
|
960
|
-
${gtd_system:+--system-prompt "$gtd_system"} \
|
|
961
|
-
--dangerously-skip-permissions >>"$gtd_log" 2>&1
|
|
962
|
-
done ;;
|
|
69
|
+
script) sh -c "$gtd_content" ;;
|
|
70
|
+
prompt) printf '%s' "$gtd_content" | your-agent ;;
|
|
963
71
|
esac
|
|
964
|
-
|
|
965
|
-
# `gtd land --sh` carries `settled`/`idle` alongside the script itself —
|
|
966
|
-
# one invocation tells us both what to run and whether to stop, with no
|
|
967
|
-
# second read needed to decide either.
|
|
968
|
-
out="$(gtd land --sh)"
|
|
969
|
-
eval "$out"
|
|
970
|
-
printf '%s\n' "$gtd_script" | sh
|
|
971
|
-
if [ "${gtd_settled:-}" = true ]; then
|
|
972
|
-
[ "${gtd_idle:-}" = true ] && gtd next
|
|
973
|
-
exit 0
|
|
974
|
-
fi
|
|
975
|
-
beat=$((beat + 1))
|
|
72
|
+
gtd land
|
|
976
73
|
done
|
|
977
74
|
```
|
|
978
75
|
|
|
979
|
-
|
|
980
|
-
`
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
preamble makes `eval` self-contained. The very first beat lands even when
|
|
985
|
-
`gtd_idle` is already true, so a workflow whose initial state declares its own
|
|
986
|
-
clean-tree `"C"` pattern still gets a chance to fire before the driver calls it
|
|
987
|
-
done. `kind: "stalled"` prints `$gtd_content` (the diagnosis itself) to stderr
|
|
988
|
-
and stops — no re-invocation needed, since the beat already fetched it;
|
|
989
|
-
`message` halts unless it is the opening beat, which the human's own
|
|
990
|
-
re-invocation authored and which therefore lands like any other decision (see
|
|
991
|
-
[Driving the loop](#driving-the-loop) above — this is the one place the driver,
|
|
992
|
-
not gtd, decides, because "has the human read this gate" is run-scoped knowledge
|
|
993
|
-
gtd deliberately does not keep); `capture` lands a human's already-made edit
|
|
994
|
-
outright, no display needed; `script` runs `$gtd_content` in the driver;
|
|
995
|
-
`prompt` sends it to the agent over stdin with
|
|
996
|
-
`$gtd_session_id`/`$gtd_model`/`$gtd_system` mapped onto the agent's session
|
|
997
|
-
flags — trying `resume`'s hinted flag first and falling back to the other on
|
|
998
|
-
failure, since the session id is derived, not remembered (see
|
|
999
|
-
[Driving the loop](#driving-the-loop) above) — and its own `$gtd_validate`
|
|
1000
|
-
script's output re-prompted verbatim on failure (the driver owns only the retry
|
|
1001
|
-
cap). Every optional variable (`$gtd_model`, `$gtd_system`, `$gtd_validate`,
|
|
1002
|
-
`$gtd_session_resume`, `$gtd_idle`, `$gtd_settled`) is read as `${var:-}` or
|
|
1003
|
-
`${var:+...}` — under `set -u`, the `--sh` document's own `unset` preamble makes
|
|
1004
|
-
an absent field genuinely unset, not empty, so a bare `$gtd_model` would abort
|
|
1005
|
-
the driver. Every landed turn is executed — and reported — by the emitted
|
|
1006
|
-
scripts themselves, via `gtd land --sh`'s own `$gtd_script` field, captured then
|
|
1007
|
-
piped to `sh`. `$gtd_settled` ends a run that has nothing left to do: a no-op at
|
|
1008
|
-
a `script` rest settles right where it rests (stop immediately, nothing more to
|
|
1009
|
-
read), while an ordinary or squash landing that finishes the whole process
|
|
1010
|
-
instead resolves `$gtd_idle` on the FOLLOWING beat's own `gtd next --sh` read —
|
|
1011
|
-
the reference driver reads once more (a plain `gtd next`) only to DISPLAY that
|
|
1012
|
-
gate's own message, the decision to stop already made from `$gtd_settled`/
|
|
1013
|
-
`$gtd_idle` alone.
|
|
1014
|
-
|
|
1015
|
-
This paste passes `$gtd_system` on argv, and POSIX guarantees only 4 KB of argv
|
|
1016
|
-
(`ARG_MAX`) — real systems are far higher (roughly 1 MB on both macOS and
|
|
1017
|
-
Linux), and the bundled personas are only a few KB each, so this works
|
|
1018
|
-
everywhere in practice. A driver shipping much larger system prompts, or
|
|
1019
|
-
targeting a platform sitting at the POSIX floor, should write the string to a
|
|
1020
|
-
file and pass that instead: gtd's own contract ends at emitting `gtd_system` as
|
|
1021
|
-
a string, and how a driver hands that string to its agent CLI — argv, a temp
|
|
1022
|
-
file, or something else — is the driver's own call, since it alone knows its
|
|
1023
|
-
platform's argv limit and whether it has a writable temp directory; gtd adds no
|
|
1024
|
-
flag, no file, and no mechanism of its own for this. (The doc-test below only
|
|
1025
|
-
proves this paste parses and runs against a `claude` shim — not that the flag is
|
|
1026
|
-
spelled or placed correctly; that's a `claude --help` check plus one live run,
|
|
1027
|
-
not something the green suite can catch.)
|
|
1028
|
-
|
|
1029
|
-
### The self-validation gate
|
|
1030
|
-
|
|
1031
|
-
After an agent turn at a state that declares `file:`+`mode:`, run the script:
|
|
1032
|
-
either `gtd next --json`'s own embedded `.validate` field (present at every
|
|
1033
|
-
`prompt` beat that hands over a validatable file), or, standalone,
|
|
1034
|
-
`gtd validate`'s own plain-text output — both resolve the exact same script from
|
|
1035
|
-
one shared resolver. This field is now populated even at a FIRST-WRITE beat,
|
|
1036
|
-
before the steering file exists at all: the script itself carries a leading
|
|
1037
|
-
`[ -f <file> ] || exit 0` guard rather than gtd checking existence ahead of
|
|
1038
|
-
time, so a driver's `while [ -n "$gtd_validate" ]` repair loop is armed from the
|
|
1039
|
-
very first turn at a state, not just the second and later ones. Exit 0 means the
|
|
1040
|
-
file is well-formed (or genuinely doesn't exist yet) — proceed to `gtd land`. A
|
|
1041
|
-
non-zero exit usually means the script's own captured output IS a complete,
|
|
1042
|
-
ready-to-send fix prompt (an instruction plus the findings, see `src/Emit.ts`'s
|
|
1043
|
-
`failurePromptWrapper`): send it back to the same agent session verbatim, and
|
|
1044
|
-
cap how many fix attempts you allow yourself — the driver owns that retry count,
|
|
1045
|
-
not gtd. Landing never TRUSTS that you validated, either: the emitted `land`
|
|
1046
|
-
script carries the same format/validate commands ahead of its own commit and
|
|
1047
|
-
fails without committing when they fail, so a malformed file is never captured
|
|
1048
|
-
whether or not you ran the validate script first.
|
|
1049
|
-
|
|
1050
|
-
A non-zero exit can ALSO mean something the fix loop cannot fix: a mode whose
|
|
1051
|
-
`format:` command breaks its own validator, a config bug gtd detects by
|
|
1052
|
-
formatting a canonical sample and re-checking it before the real file's own
|
|
1053
|
-
findings are ever reported. That message is unmistakably different — it says
|
|
1054
|
-
this is a CONFIGURATION BUG, not the steering file, and tells the agent to stop
|
|
1055
|
-
and end its turn rather than edit anything. gtd's exit-code contract stays
|
|
1056
|
-
closed at five numbers (below), so nothing distinguishes the two cases at the
|
|
1057
|
-
process level — a driver's fix loop is still the CORRECT thing to run in both
|
|
1058
|
-
cases, it just can never win a contradiction: its 3-attempt cap is what ends
|
|
1059
|
-
one, not a fix. Reading the message text before re-prompting is what keeps an
|
|
1060
|
-
agent from thrashing on a document it cannot fix.
|
|
1061
|
-
|
|
1062
|
-
### Failure taxonomy and recovery
|
|
1063
|
-
|
|
1064
|
-
Several different things can go wrong, and they mean different things — and most
|
|
1065
|
-
non-zero-looking exits are not a failure at all:
|
|
1066
|
-
|
|
1067
|
-
- **`gtd` itself exits 1.** Nothing was attempted — this is a refusal (a guard
|
|
1068
|
-
rejected the turn). No script was ever produced.
|
|
1069
|
-
- **`gtd` itself exits 2.** A usage error — nothing was even attempted, the
|
|
1070
|
-
invocation itself was wrong (unknown option/command, bad arity, a scope
|
|
1071
|
-
violation).
|
|
1072
|
-
- **`gtd land` succeeds (exit 0) but doesn't settle.** This is NOT a failure:
|
|
1073
|
-
whose turn is next lives in the FOLLOWING `gtd next`'s own `kind` field, not
|
|
1074
|
-
in `gtd land`'s exit code (see [Exit codes](#exit-codes)) — and `gtd land`'s
|
|
1075
|
-
own stdout still carries a script (a print-only note, or a genuine
|
|
1076
|
-
retain+rewind, or an ordinary commit) that a driver must still run.
|
|
1077
|
-
- **An emitted script exits non-zero when YOU run it.** Something may have
|
|
1078
|
-
partially happened — e.g. a `gtd_retry`-wrapped git write landed but a later
|
|
1079
|
-
step in the same script failed.
|
|
1080
|
-
|
|
1081
|
-
Recovery is the same in every case: **ask gtd again** (`gtd next`, then land).
|
|
1082
|
-
It re-reads the real repository state fresh every time — never a cached plan —
|
|
1083
|
-
and emits whatever still needs to happen from there. This works because every
|
|
1084
|
-
emitted script opens by asserting its own precondition
|
|
1085
|
-
(`[ "$(git rev-parse --verify --quiet HEAD 2>/dev/null)" = <expected> ] || { ...; exit 1; }`,
|
|
1086
|
-
and the same shape for a review window's saved ref — see `src/Emit.ts`'s
|
|
1087
|
-
`headAssertion`/`reviewWindowAssertion`), so a script generated against a
|
|
1088
|
-
repository state that has since moved refuses loudly instead of corrupting
|
|
1089
|
-
anything. **Emitted scripts are re-runnable**: this is the single most important
|
|
1090
|
-
property for a driver's recovery logic. Re-running a script that already fully
|
|
1091
|
-
applied is a no-op (its git writes are `--allow-empty` commits, idempotent ref
|
|
1092
|
-
updates, and tolerant staged-restore calls), and re-running one that only
|
|
1093
|
-
partially applied resumes correctly, because the precondition either still holds
|
|
1094
|
-
(nothing landed yet — safe to retry verbatim) or gtd's next invocation reads the
|
|
1095
|
-
new real state and emits a fresh script for what remains. A driver never needs
|
|
1096
|
-
its own retry/resume logic beyond "if the script failed, ask gtd again."
|
|
1097
|
-
|
|
1098
|
-
### Prerequisites
|
|
1099
|
-
|
|
1100
|
-
- **A POSIX `sh`** (dash, ash, bash's own POSIX mode, etc.) — gtd's own emitted
|
|
1101
|
-
scripts (`gtd land`, `gtd --entry <state>`, `gtd abandon`, `gtd restore`) are
|
|
1102
|
-
POSIX sh; captured, then piped into it (see
|
|
1103
|
-
[Writing your own driver](#writing-your-own-driver) above).
|
|
1104
|
-
- **`gtd` on `PATH`** — a mode's seeded `validate:` command (the one the
|
|
1105
|
-
compiler fills in for the built-in `qa`/`review` formats) is literally the
|
|
1106
|
-
string `gtd check <mode> '<file>'`, invoked by NAME from inside an emitted
|
|
1107
|
-
script, not by absolute path (see `src/SteeringFormats.ts`'s
|
|
1108
|
-
`seededValidateCommand`). This is a deliberate trade: a readable, overridable,
|
|
1109
|
-
copy-pasteable command in exchange for depending on shell name resolution at
|
|
1110
|
-
the moment the script runs. The sharp edge: if the `gtd` binary you invoked to
|
|
1111
|
-
GENERATE the script differs from the `gtd` that resolves on `PATH` when the
|
|
1112
|
-
script later RUNS (a locally-built dev binary vs. a globally-installed
|
|
1113
|
-
release, say), you can get version skew between the two — the command that
|
|
1114
|
-
validates may not be the command that decided. Keep the two in sync (one `gtd`
|
|
1115
|
-
on `PATH`, consistently) if you care about that gap.
|
|
1116
|
-
|
|
1117
|
-
### The normalization-only contract on `format:`
|
|
1118
|
-
|
|
1119
|
-
A mode's `format:` command may reformat a steering file — whitespace, wrapping,
|
|
1120
|
-
reordering — but must NEVER change what a land-capture guard would decide. gtd's
|
|
1121
|
-
guards (the review-doc check, the feedback-progress check, the
|
|
1122
|
-
answer-completeness check, the require-revert check — `src/StepGuards.ts`)
|
|
1123
|
-
decide ONCE, against whichever bytes are on disk at the moment `gtd land` runs,
|
|
1124
|
-
which may be before OR after an emitted script's own `format:` line has run (the
|
|
1125
|
-
script runs `format:` then `validate:` then the commit — see
|
|
1126
|
-
`src/SteeringMode.ts`'s `renderSteeringCommands` — but gtd's decision and the
|
|
1127
|
-
driver's script execution are different processes at different times, so there's
|
|
1128
|
-
no guaranteed ordering between "gtd decided" and "the script formatted"). That's
|
|
1129
|
-
only safe because every built-in guard judges only the content it explicitly
|
|
1130
|
-
cares about, not incidental formatting around it — the feedback-progress guard,
|
|
1131
|
-
for instance, only checks whether a deleted file's trimmed first line is the
|
|
1132
|
-
`NOTHING ACTIONABLE` sentinel, so reindenting the rest of it changes nothing the
|
|
1133
|
-
guard reads. If you plug in your own `format:` command, the same rule binds it:
|
|
1134
|
-
a formatter that also changes meaning — stripping a paragraph a guard reads —
|
|
1135
|
-
makes the guard's decision and the file's actual content disagree, and gtd will
|
|
1136
|
-
not catch that for you.
|
|
1137
|
-
|
|
1138
|
-
One case never runs your `format:` (or `validate:`) at all: a step whose diff
|
|
1139
|
-
DELETES the state's own `file:`. Deleting it is a legitimate outcome — a review
|
|
1140
|
-
sign-off's whole diff is the review doc's deletion — and there is nothing left
|
|
1141
|
-
to format. Emitting the command anyway would make such a step unlandable, since
|
|
1142
|
-
`format:` is the first line of a `set -eu` script and a formatter like
|
|
1143
|
-
`prettier --write` exits non-zero on a path that is not there.
|
|
1144
|
-
|
|
1145
|
-
### Built-in steering formats are ordinary modes
|
|
1146
|
-
|
|
1147
|
-
`qa` and `review` are gtd's two built-in steering-file formats (parsed and
|
|
1148
|
-
validated in-process because `gtd lsp` needs the same parsers for live
|
|
1149
|
-
diagnostics), but their `validate:` is not hardcoded or hidden: the compiler
|
|
1150
|
-
SEEDS every workflow's `modes:` map with `qa`/`review` entries whose `validate:`
|
|
1151
|
-
is the same `gtd check <mode> '<file>'` string described above
|
|
1152
|
-
(`src/PatternConfig.ts`, `src/SteeringFormats.ts`'s `seededValidateCommand`).
|
|
1153
|
-
That seeded command is visible in `gtd visualize`'s compiled model and in the
|
|
1154
|
-
editor JSON schema like any other mode, and it's overridable the same way any
|
|
1155
|
-
mode is — declare `modes: { qa: { validate: "your-own-command" } }` (in the
|
|
1156
|
-
workflow or in `.gtdrc`) and your command displaces the seed; declaring only a
|
|
1157
|
-
`format:` for `qa`/`review` composes with the seeded `validate:` rather than
|
|
1158
|
-
replacing it. There is no special-cased built-in behavior a driver needs to know
|
|
1159
|
-
about beyond the ordinary mode-resolution rules already documented under
|
|
1160
|
-
[The `workflow:` key](#the-workflow-key).
|
|
1161
|
-
|
|
1162
|
-
## Configuration
|
|
1163
|
-
|
|
1164
|
-
gtd reads an optional `.gtdrc` config file via
|
|
1165
|
-
[cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no `workflow:`
|
|
1166
|
-
configured anywhere in the cwd→home config chain, the bundled unified workflow
|
|
1167
|
-
is used automatically, so a state command works out of the box with no config at
|
|
1168
|
-
all. Supported filenames (searched in this order):
|
|
1169
|
-
|
|
1170
|
-
- `.gtdrc`
|
|
1171
|
-
- `.gtdrc.json`
|
|
1172
|
-
- `.gtdrc.yaml`
|
|
1173
|
-
- `.gtdrc.yml`
|
|
1174
|
-
- `gtd.config.json`
|
|
1175
|
-
- `gtd.config.yaml`
|
|
1176
|
-
|
|
1177
|
-
### Schema
|
|
1178
|
-
|
|
1179
|
-
`.gtdrc` has exactly three blessed top-level keys:
|
|
1180
|
-
|
|
1181
|
-
- **`workflow`** (object, optional) — the whole machine definition (its states,
|
|
1182
|
-
plus its own `vars:` defaults and `modes:`). Absent = gtd's built-in default
|
|
1183
|
-
is used. Declare it to fully REPLACE that default with your own machine (there
|
|
1184
|
-
is no `extends`/merge).
|
|
1185
|
-
- **`vars`** (object, optional) — a flat `name -> scalar` map, one layer of the
|
|
1186
|
-
merged `it.vars` every template sees.
|
|
1187
|
-
- **`modes`** (object, optional) — steering-file modes (`format:`/`validate:`
|
|
1188
|
-
shell commands), layered over the active workflow's own `modes:` and gtd's
|
|
1189
|
-
built-in validators, so a project can plug in its formatter or linter without
|
|
1190
|
-
re-declaring that mode on the workflow itself.
|
|
1191
|
-
- **`$schema`** (string, optional) — stripped before validation, so it never
|
|
1192
|
-
counts as an unknown key. Point it at the published schema for editor-backed
|
|
1193
|
-
autocompletion (this is what `gtd init` writes):
|
|
1194
|
-
|
|
1195
|
-
```
|
|
1196
|
-
https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json
|
|
1197
|
-
```
|
|
1198
|
-
|
|
1199
|
-
That URL serves `schema.json` straight out of the published npm tarball, so it
|
|
1200
|
-
always matches the latest release. Pin it to a major with
|
|
1201
|
-
`@pmelab/gtd@8/schema.json`, or point at your own install
|
|
1202
|
-
(`./node_modules/@pmelab/gtd/schema.json`) to work offline.
|
|
1203
|
-
|
|
1204
|
-
Any other top-level key is **rejected**. The engine blesses no VARIABLE NAMES
|
|
1205
|
-
either — `testCommand` is workflow-authored data like any other `it.vars` entry,
|
|
1206
|
-
not a special key gtd interprets.
|
|
1207
|
-
|
|
1208
|
-
### The `workflow:` key
|
|
1209
|
-
|
|
1210
|
-
A declared `workflow:` key fully REPLACES gtd's built-in default. The built-in
|
|
1211
|
-
default is itself a YAML asset (`src/workflows/unified.yaml`) compiled through
|
|
1212
|
-
the exact same compiler your own `workflow:` value goes through — no privileged
|
|
1213
|
-
code path. Its shape:
|
|
1214
|
-
|
|
1215
|
-
```yaml
|
|
1216
|
-
workflow:
|
|
1217
|
-
vars: # optional — the workflow's own declared `it.vars` defaults
|
|
1218
|
-
anyKey: anyScalarValue
|
|
1219
|
-
modes: # optional — steering-file modes a state's `mode:` may name
|
|
1220
|
-
<name>:
|
|
1221
|
-
format: <shell command> # both optional — {} is the format-only tier
|
|
1222
|
-
validate: <shell command>
|
|
1223
|
-
entry:
|
|
1224
|
-
default: <machine name> # which machine is the ROOT instance
|
|
1225
|
-
machines:
|
|
1226
|
-
<name>:
|
|
1227
|
-
model: <string> # optional, opaque harness hint — stamped onto every one of THIS machine's own `prompt` states; declared ONCE per machine, never per state
|
|
1228
|
-
params: [<param>, ...] # optional, advisory — documents which $params a caller may bind
|
|
1229
|
-
entry: <local or ref key> # this machine's own default local, resolved recursively
|
|
1230
|
-
states:
|
|
1231
|
-
<local>:
|
|
1232
|
-
actor: <string> # forbidden on a commit state, required otherwise
|
|
1233
|
-
script: <string> # exactly one of script/prompt/message/commit
|
|
1234
|
-
prompt: <string>
|
|
1235
|
-
message: <string>
|
|
1236
|
-
commit: <string>
|
|
1237
|
-
on: # a mapping, DECLARATION ORDER PRESERVED
|
|
1238
|
-
"<pattern>": <targetState> # short form
|
|
1239
|
-
"<pattern>": {
|
|
1240
|
-
to: <targetState>,
|
|
1241
|
-
describe: <sentence>,
|
|
1242
|
-
action: <label>,
|
|
1243
|
-
} # description/action
|
|
1244
|
-
retry:
|
|
1245
|
-
max: <number>
|
|
1246
|
-
otherwise: <targetState>
|
|
1247
|
-
label: <string> # optional, opaque display name passed through `gtd next --json`
|
|
1248
|
-
file: <string> # optional, an Eta template naming the state's steering file RELATIVE to ".gtd/" — the compiler prepends that directory automatically
|
|
1249
|
-
mode: <modeName> # optional, requires "file" — must be declared in `modes:` (qa/review are seeded for you; everything else, including prose, you declare)
|
|
1250
|
-
reviewWindow: true # optional — open the review checkout window at rest here
|
|
1251
|
-
reviewBase: true # optional — anchor the review window's diff base to this state's most-recent commit
|
|
1252
|
-
# reviewBase: <Eta template> # OR a template — rendered (only meaningful entering via --entry) to a commitish that fixes the WHOLE PROCESS's diff base
|
|
1253
|
-
requireProgress: true # optional, requires "file" — refuse a turn whose only change deletes this state's own `file:`
|
|
1254
|
-
answerGate: true # optional, requires "file" — refuse a turn until every open question in the (qa-mode) `file:` is answered
|
|
1255
|
-
requireRevert: true # optional, requires "file" — refuse a turn until the human's review-round paths actually match the review base's parent
|
|
1256
|
-
entry: true # optional — an EXTRA reachability root (`entries.manual`), enterable via `gtd --entry <this state's qualified name>` — NOT a precondition for `--entry` (any declared, non-commit state is a valid target)
|
|
1257
|
-
<local>: { machine: <name>, with: { <param>: <value> } } # a REFERENCE — instantiates <name> as a child, qualified as `<local>.<childLocal>`
|
|
1258
|
-
```
|
|
1259
|
-
|
|
1260
|
-
There is no `memory:` key anywhere in this shape — a state's memory scope is
|
|
1261
|
-
never authored, only computed from its position in the machine tree (see
|
|
1262
|
-
[Driving the loop](#driving-the-loop) above for the key format and how it
|
|
1263
|
-
derives a driver's session id).
|
|
1264
|
-
|
|
1265
|
-
The top-level `entry:` key (naming the root machine, `entry.default`) and a
|
|
1266
|
-
state's own `entry: true` flag are the same word at two different levels, by
|
|
1267
|
-
design: one selects the workflow's root machine, the other opts one state in as
|
|
1268
|
-
an extra manual entry point.
|
|
1269
|
-
|
|
1270
|
-
A workflow is authored as a TREE of reusable, parameterized machines — a
|
|
1271
|
-
gate/loop written once and instantiated several times with different `with:`
|
|
1272
|
-
bindings (dedup), or a complex cluster grouped under one name for source
|
|
1273
|
-
comprehension (encapsulation). Every reference is expanded at load time into
|
|
1274
|
-
concrete, qualified states (`<local>.<childLocal>`, however deep) before the
|
|
1275
|
-
engine ever sees the definition — see `src/Machines.ts` for the mechanism.
|
|
1276
|
-
MACHINE BOUNDARIES ARE THE UNIT OF CONVERSATIONAL IDENTITY: a machine that holds
|
|
1277
|
-
an identity (a planner or a coder persona) declares its own `model:` once, at
|
|
1278
|
-
the machine level, instead of repeating it per state — and, per the memory rule
|
|
1279
|
-
above, two references to the SAME machine (a dedup instantiation) are always two
|
|
1280
|
-
independent instances with two independent memory scopes, never one shared
|
|
1281
|
-
conversation across both call sites.
|
|
1282
|
-
|
|
1283
|
-
Besides `it.vars` (below), a `script`/`prompt`/`message`/`commit` template sees:
|
|
1284
|
-
|
|
1285
|
-
- **`it.startCommit`** — the process's diff base (the commit the current process
|
|
1286
|
-
started from, or the base a `--var reviewBase=<commitish>` entry resolved to).
|
|
1287
|
-
- **`it.reviewBase`** — the previous review round's boundary, falling back to
|
|
1288
|
-
`it.startCommit` on a first review. In the bundled template, an actionable
|
|
1289
|
-
round's NEXT review therefore covers the revert of the human's own lines
|
|
1290
|
-
(`re-unwind`) followed by the whole lap that re-derives and re-implements them
|
|
1291
|
-
— the net diff is the final implementation, the right thing to review, but no
|
|
1292
|
-
longer the tiny "just the fixes" delta a quick fix-and-re-review would have
|
|
1293
|
-
shown.
|
|
1294
|
-
- **`it.retainedBase`** — the process's trace/retry boundary, what a squash
|
|
1295
|
-
actually keeps (never moved by a review entry's fixed base).
|
|
1296
|
-
- **`it.currentCommit`** / **`it.previousCommit`** — HEAD's hash and its parent,
|
|
1297
|
-
at render time.
|
|
1298
|
-
|
|
1299
|
-
A template never sees rendered diff CONTENT — no field carries a diff. It names
|
|
1300
|
-
a base and leaves the agent to run `git diff <base>` itself; this keeps every
|
|
1301
|
-
render cheap (no diff computed on `gtd next`/`gtd lsp`) and the prompt small and
|
|
1302
|
-
cacheable.
|
|
1303
|
-
|
|
1304
|
-
Authoring or editing a workflow with a coding agent? `skills/authoring/SKILL.md`
|
|
1305
|
-
is the agent-facing contract for producing a valid `workflow:` — the state
|
|
1306
|
-
model, pattern grammar, load-time rules, and how to verify a change compiles.
|
|
1307
|
-
|
|
1308
|
-
> **Upgrading from a pre-8.2 `workflow:`?** The old flat `states:` shape (with a
|
|
1309
|
-
> per-state `initial: true`/`reviewEntry: true`/`fixEntry: true` flag) is no
|
|
1310
|
-
> longer accepted — finish or `gtd abandon` any in-flight process before
|
|
1311
|
-
> upgrading, since the old and new shapes aren't compatible mid-process. Wrap
|
|
1312
|
-
> your states under a single
|
|
1313
|
-
> `machines: { <name>: { entry: <initial state>, states: {...} } }` and declare
|
|
1314
|
-
> `entry: { default: <name> }` at the top level (moving any
|
|
1315
|
-
> `reviewEntry`/`fixEntry` state to a plain per-state `entry: true` flag,
|
|
1316
|
-
> entered via `gtd --entry <state>` — see the next note).
|
|
1317
|
-
|
|
1318
|
-
> **Upgrading a `workflow:` that still declares `entry.review`/`entry.fix`?**
|
|
1319
|
-
> Those two keys, and the `gtd review <commitish>`/`gtd fix` commands that used
|
|
1320
|
-
> them, are gone. Replace `entry.review: <target>`/`entry.fix: <target>` with a
|
|
1321
|
-
> plain `entry: true` flag on that same state, and enter it with
|
|
1322
|
-
> `gtd --entry <state>` instead of the removed commands.
|
|
1323
|
-
> `gtd review <commitish>` required a clean tree and a `<commitish>` argument;
|
|
1324
|
-
> the replacement instead captures whatever is pending in the working tree (just
|
|
1325
|
-
> like an ordinary `gtd land`) and takes the commitish as a
|
|
1326
|
-
> `--var reviewBase=<commitish>` override consumed by that state's own
|
|
1327
|
-
> template-form `reviewBase:` (see the `workflow:` shape above and
|
|
1328
|
-
> [`gtd --entry`](#commands)). `gtd fix` likewise becomes
|
|
1329
|
-
> `gtd --entry <the state that was entry.fix>` (e.g. the bundled template's
|
|
1330
|
-
> `gtd --entry fix-precheck`).
|
|
1331
|
-
|
|
1332
|
-
> **Upgrading a `workflow:` that still declares a per-state `model:` or
|
|
1333
|
-
> `memory:`?** `model:` moved from a state key to a MACHINE key: declare it once
|
|
1334
|
-
> on the `machines.<name>:` entry instead of on every one of that machine's
|
|
1335
|
-
> states — it is stamped onto every one of that machine's own `prompt` states
|
|
1336
|
-
> automatically (see the `workflow:` shape above). A state that still declares
|
|
1337
|
-
> its own `model:` is a load error naming the machine to move it to, never a
|
|
1338
|
-
> silently ignored key. `memory:` is gone outright, with **no** replacement key
|
|
1339
|
-
> — a workflow author simply removes it; a state's memory scope is now computed
|
|
1340
|
-
> from its position in the machine tree instead of authored (see
|
|
1341
|
-
> [Driving the loop](#driving-the-loop)). The bundled template was also
|
|
1342
|
-
> restructured so machine boundaries line up with this new identity model,
|
|
1343
|
-
> renaming twenty-two states:
|
|
1344
|
-
>
|
|
1345
|
-
> - `building` → `build.building`
|
|
1346
|
-
> - `decompose` → `build.decompose` → `design.decompose`
|
|
1347
|
-
> - `squashing` → `build.squashing`
|
|
1348
|
-
> - `review.building` → `build.addressing`
|
|
1349
|
-
> - `packages.building` → `packages.item.building`
|
|
1350
|
-
> - `packages.closing` → `packages.item.closing`
|
|
1351
|
-
> - `packages.health.check` → `packages.item.health.check`
|
|
1352
|
-
> - `packages.health.fix` → `packages.item.fix-suite`
|
|
1353
|
-
> - `packages.health.escalate` → `packages.item.health.escalate`
|
|
1354
|
-
> - `packages.spec.review` → `packages.item.spec.review`
|
|
1355
|
-
> - `packages.spec.fix` → `packages.item.fix-spec`
|
|
1356
|
-
> - `build.check` → `build.health.check`
|
|
1357
|
-
> - `build.escalate` → `build.health.escalate`
|
|
1358
|
-
> - `review.reviewing` → `build.review.reviewing`
|
|
1359
|
-
> - `review.await-review` → `build.review.await-review`
|
|
1360
|
-
> - `review.deciding` → `build.review.deciding`
|
|
1361
|
-
> - `review.collecting` → `build.review.collecting`
|
|
1362
|
-
> - `product.author` → `design.product-author`
|
|
1363
|
-
> - `product.answer` → `design.product-answer`
|
|
1364
|
-
> - `technical.author` → `design.technical-author`
|
|
1365
|
-
> - `technical.answer` → `design.technical-answer`
|
|
1366
|
-
>
|
|
1367
|
-
> (`decompose`'s two hops both land in this same release, so a process upgrading
|
|
1368
|
-
> from before either restructure only ever sees one hop: `decompose` →
|
|
1369
|
-
> `design.decompose`.) Because of these renames, an in-flight process left
|
|
1370
|
-
> resting at one of the old qualified state names can no longer be resumed after
|
|
1371
|
-
> upgrading — those names no longer exist in the definition, and gtd refuses
|
|
1372
|
-
> loudly rather than silently treating the rest as idle. Run `gtd abandon` to
|
|
1373
|
-
> discard it and start over (or finish the process on the pre-upgrade workflow
|
|
1374
|
-
> version first).
|
|
1375
|
-
|
|
1376
|
-
> **Upgrading a `workflow:` from the old two-flow (`.gtd/TODO.md` vs.
|
|
1377
|
-
> `.gtd/REQUIREMENTS.md`) shape?** The bundled template collapsed that fork into
|
|
1378
|
-
> one flow: `idle` now has a single outgoing edge — any change at all starts the
|
|
1379
|
-
> process, never a fork on which steering file you create — and
|
|
1380
|
-
> `plan-gate.check`/`spec-gate.check` merged into one shared `start-gate.check`.
|
|
1381
|
-
> The old plan-iteration machine's `plan.planning`/`plan.await-plan` states and
|
|
1382
|
-
> its monolithic `build.building` state are gone outright; every concern now
|
|
1383
|
-
> goes through the same per-package build queue instead. Renamed:
|
|
1384
|
-
> `design.product-author` → `design.triage`, `design.product-answer` →
|
|
1385
|
-
> `design.gate.answer`, `design.technical-author` → `architecture.author`,
|
|
1386
|
-
> `design.technical-answer` → `architecture.gate.answer`, `design.decompose` →
|
|
1387
|
-
> `architecture.decompose` (architecture is now its own sibling machine with its
|
|
1388
|
-
> own memory scope, not nested under `design`). The `prose` mode is gone and
|
|
1389
|
-
> there is no more free-form _plan_ file — `todoFile` itself came back later
|
|
1390
|
-
> with a different job: `idle`'s own mode-less `file:` hint, gtd's sketch pad
|
|
1391
|
-
> that no state writes or reads. As with every rename above, an in-flight
|
|
1392
|
-
> process resting at one of the old names can no longer be resumed;
|
|
1393
|
-
> `gtd abandon` it (or finish it on the pre-upgrade workflow version) before
|
|
1394
|
-
> upgrading.
|
|
1395
|
-
|
|
1396
|
-
> **Upgrading a `workflow:` that still references `build.addressing`?** That
|
|
1397
|
-
> state is REMOVED, not renamed — the implementer's own follow-through on review
|
|
1398
|
-
> feedback is gone outright, since an actionable review round is now re-planned
|
|
1399
|
-
> from scratch through a new root-level `re-unwind` state (reverting the human's
|
|
1400
|
-
> hand-edit) instead of being built upon. A non-actionable round (an approving
|
|
1401
|
-
> remark with no code edit) short-circuits straight to `build.squashing` instead
|
|
1402
|
-
> of spending a lap on nothing. As with every rename above, an in-flight process
|
|
1403
|
-
> resting at `build.addressing` can no longer be resumed after upgrading;
|
|
1404
|
-
> `gtd abandon` it (or finish it on the pre-upgrade workflow version) first.
|
|
1405
|
-
|
|
1406
|
-
### Variables
|
|
1407
|
-
|
|
1408
|
-
Every template — `script`/`prompt`/`message`/`commit`, a machine's own `model:`,
|
|
1409
|
-
and a state's `file:` — sees `it.vars`: a flat `Record<string, string>`
|
|
1410
|
-
assembled from four layers, **later wins**:
|
|
1411
|
-
|
|
1412
|
-
1. **The workflow's own `vars:` key** (sibling to `entry:`/`machines:`) — the
|
|
1413
|
-
workflow author's declared defaults. The unified template declares
|
|
1414
|
-
`vars: { testCommand: "npm test" }`, read by `build.health.check`'s script as
|
|
1415
|
-
`<%~ it.vars.testCommand %>`.
|
|
1416
|
-
2. **A top-level `.gtdrc` `vars:` key** (a sibling of `workflow:`, NOT nested
|
|
1417
|
-
inside it) — per-repo tuning without redefining the whole workflow.
|
|
1418
|
-
3. **The current process's entry `--var` overrides**, if it was started via
|
|
1419
|
-
`gtd --entry <state>` — repeatable `--var <name>=<value>` flags fixed at the
|
|
1420
|
-
moment of entry and recorded as `Gtd-Var: <name>=<value>` trailers on the
|
|
1421
|
-
process's oldest commit, re-parsed on every turn for as long as that process
|
|
1422
|
-
is underway. Each `--var` name must already be declared by layer 1 or 2; an
|
|
1423
|
-
undeclared name is a usage error, not a silent no-op.
|
|
1424
|
-
4. **`GTD_<UPPERCASE-name>` environment variables** — highest precedence,
|
|
1425
|
-
checked at every invocation, case-insensitively against each name already
|
|
1426
|
-
declared by layers 1–3: `GTD_TESTCOMMAND` overrides `testCommand`. The
|
|
1427
|
-
environment can only OVERRIDE a name an earlier layer already declared — a
|
|
1428
|
-
`GTD_*` var matching no declared name is silently ignored.
|
|
1429
|
-
|
|
1430
|
-
Values in layers 1–2 must be YAML scalars (string/number/boolean), coerced to
|
|
1431
|
-
strings at load time; an object or array value is a load error. A `--var` value
|
|
1432
|
-
(layer 3) is always a single-line string as given on the command line.
|
|
1433
|
-
|
|
1434
|
-
```yaml
|
|
1435
|
-
# .gtdrc — overriding the unified template's testCommand
|
|
1436
|
-
vars:
|
|
1437
|
-
testCommand: npm run test:ci
|
|
1438
|
-
```
|
|
1439
|
-
|
|
1440
|
-
```bash
|
|
1441
|
-
# highest precedence — beats both the workflow default and the .gtdrc value above
|
|
1442
|
-
GTD_TESTCOMMAND="npm run test -- --bail" gtd next
|
|
1443
|
-
```
|
|
1444
|
-
|
|
1445
|
-
#### The voice
|
|
1446
|
-
|
|
1447
|
-
gtd ships its own writing voice for the files it generates — the default, not an
|
|
1448
|
-
opt-in. It is a specialisation of the "Spartan" output style from
|
|
1449
|
-
[alexgreensh/attention-span](https://github.com/alexgreensh/attention-span)
|
|
1450
|
-
(AGPL-3.0), written from a reading of that project's version `0.6`: gtd's own
|
|
1451
|
-
prose stating the same density discipline, rewritten for deliverables (files
|
|
1452
|
-
that run as long as the work needs) rather than chat replies. No upstream text
|
|
1453
|
-
ships in gtd's bundle. This is a point-in-time derivation with no refresh
|
|
1454
|
-
mechanism — it will silently go stale as upstream moves on, and nothing in gtd
|
|
1455
|
-
will notice.
|
|
1456
|
-
|
|
1457
|
-
It is two independently-overridable variables, each an ordinary `vars:` entry
|
|
1458
|
-
that can be overridden or blanked through the same layers as any other (a
|
|
1459
|
-
top-level `.gtdrc` `vars:` key, or the matching `GTD_STYLEBLOCK` /
|
|
1460
|
-
`GTD_STYLEFORMATCONTRACT` environment variable):
|
|
1461
|
-
|
|
1462
|
-
- **`styleBlock`** — the voice itself, injected at all seven prompt states that
|
|
1463
|
-
generate content, not just three: the free-prose ones — the `.gtd/packages/`
|
|
1464
|
-
package files (`architecture.decompose`), `.gtd/SPEC_FEEDBACK.md`
|
|
1465
|
-
(`packages.item.spec.review`), and `.gtd/COMMIT_MSG.md` (`build.squashing`) —
|
|
1466
|
-
plus the four machine-parsed states named in the next bullet. Blanking
|
|
1467
|
-
`GTD_STYLEBLOCK` strips the voice from all seven, including the machine-parsed
|
|
1468
|
-
ones, not only the free-prose three. In short: why density matters before any
|
|
1469
|
-
rule, answer-first with no preamble or restatement, blunt and imperative,
|
|
1470
|
-
plain words, bold carries the load, ship the artifact bare, compressing is not
|
|
1471
|
-
dropping, one idea per block, flag risk in one blunt line, never narrate the
|
|
1472
|
-
work — and never-trim outranks every other rule in the set.
|
|
1473
|
-
- **`styleFormatContract`** — the structural override for machine-parsed files:
|
|
1474
|
-
the format contract (headings, checkbox rows, marker lines) outranks the
|
|
1475
|
-
voice, and a violation refuses the turn. It renders at the top of the prompt,
|
|
1476
|
-
ahead of the role sentence and well before any state-specific format-contract
|
|
1477
|
-
text — `styleBlock` first, then `styleFormatContract`, at the four prompt
|
|
1478
|
-
states whose output a parser reads: the two `qa`-mode steering files
|
|
1479
|
-
(`.gtd/REQUIREMENTS.md` at `design.triage`, `.gtd/ARCHITECTURE.md` at
|
|
1480
|
-
`architecture.author`) and the `review`-mode one (`.gtd/REVIEW.md` at
|
|
1481
|
-
`build.review.reviewing`), plus `.gtd/REQUIREMENTS.md` again at
|
|
1482
|
-
`build.review.collecting`, which classifies a review round straight into it.
|
|
1483
|
-
|
|
1484
|
-
Three more generated files carry no injected voice, because a script — not an
|
|
1485
|
-
agent — writes them: `.gtd/FEEDBACK.md` (verbatim test-suite output plus a HEAD
|
|
1486
|
-
stamp — the tool's content, not gtd's prose), `.gtd/NEXT.md` (a bare path, no
|
|
1487
|
-
prose to style), and `.gtd/REVIEW_RAW.md` (gtd's own prose, hand-tightened in
|
|
1488
|
-
the voice directly in `build.review.deciding`'s script rather than templated in
|
|
1489
|
-
through either variable).
|
|
1490
|
-
|
|
1491
|
-
### Lookup and precedence
|
|
1492
|
-
|
|
1493
|
-
gtd walks from the current working directory **up to your home directory** (or
|
|
1494
|
-
to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
|
|
1495
|
-
finds along the way. All found levels are **deep-merged**, with the **innermost
|
|
1496
|
-
(cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
|
|
1497
|
-
directory cascades to every checkout beneath it, while any individual checkout
|
|
1498
|
-
can still override with its own `.gtdrc`.
|
|
1499
|
-
|
|
1500
|
-
### Validation and errors
|
|
1501
|
-
|
|
1502
|
-
Config-shape problems (unknown keys, wrong types, unreadable file references)
|
|
1503
|
-
are collected together; if the shape is clean, the assembled definition is
|
|
1504
|
-
additionally run through the engine's own validation. A bad config throws
|
|
1505
|
-
**one** error listing every finding, at load time — before anything touches the
|
|
1506
|
-
repository — never partially, and never deferred to land time:
|
|
1507
|
-
|
|
1508
|
-
```
|
|
1509
|
-
workflow config:
|
|
1510
|
-
- state "idle": must declare exactly one of script/prompt/message/commit (found 2)
|
|
1511
|
-
- state "idle": "on" target "nowhere" is not a defined state
|
|
1512
|
-
```
|
|
1513
|
-
|
|
1514
|
-
Those findings include the **semantic graph checks**: every `on` target and
|
|
1515
|
-
`retry.otherwise` must name a defined state, and every state must be
|
|
1516
|
-
**reachable** from the initial state. All load failures exit **1** and write to
|
|
1517
|
-
**stderr**, never stdout.
|
|
1518
|
-
|
|
1519
|
-
Many of these problems never reach gtd at all if your editor validates against
|
|
1520
|
-
the [published schema](#schema), which fully types the `workflow:` key. The
|
|
1521
|
-
rules JSON Schema cannot express — exactly one content kind, `entry.default`
|
|
1522
|
-
resolving to a real state, targets naming defined states, reachability — remain
|
|
1523
|
-
the compiler's job at load time.
|
|
1524
|
-
|
|
1525
|
-
## Repository requirements
|
|
1526
|
-
|
|
1527
|
-
- **Single writer, linear branch.** A process's history is walked via
|
|
1528
|
-
**first-parent** commits only.
|
|
1529
|
-
- **Test/build artifacts must be gitignored.** This is **load-bearing**, not a
|
|
1530
|
-
style preference: every land decision detects "clean" via
|
|
1531
|
-
`git diff --name-status HEAD` (tracked changes) unioned with
|
|
1532
|
-
`git ls-files --others --exclude-standard` (untracked files, classified by
|
|
1533
|
-
content against the base — one that is already there with the same bytes is
|
|
1534
|
-
not a change, and a file that exists on disk is never reported as a deletion,
|
|
1535
|
-
however the index sees it), which silently omits anything matched by
|
|
1536
|
-
`.gitignore`. If a `script` state's command (or the build it triggers) writes
|
|
1537
|
-
output — a `dist/`, a coverage report, a log file — into the working tree, the
|
|
1538
|
-
tree never goes clean after a green run, and the check's `"C"` pattern never
|
|
1539
|
-
fires. Gitignore every path your scripts write before wiring gtd into a repo.
|
|
1540
|
-
- **Repository root invocation.** Every state subcommand must run from the git
|
|
1541
|
-
repository root. `--help`/`--version` (and the `help`/`version` subcommands),
|
|
1542
|
-
`lsp`, `init`, `visualize`, `check`, and `install` skip this guard entirely
|
|
1543
|
-
(`visualize` still reads the `.gtdrc` workflow, but needs no git state; `init`
|
|
1544
|
-
may even run outside a repository to seed a shared parent-dir config).
|
|
1545
|
-
- **Linked worktrees are independent.** N `git worktree` worktrees of one
|
|
1546
|
-
repository (sharing a single `.git`) each run their own gtd process: state is
|
|
1547
|
-
derived from that worktree's own HEAD, and the review checkout window's refs
|
|
1548
|
-
live in git's per-worktree `refs/worktree/gtd/*` namespace, so a review
|
|
1549
|
-
resting in one worktree neither blocks nor rewrites any other.
|
|
1550
|
-
|
|
1551
|
-
## Editor integration
|
|
1552
|
-
|
|
1553
|
-
`gtd lsp` starts an LSP server over stdio for `.gtd/` steering files — a symbol
|
|
1554
|
-
per `review`-mode chunk that still has an unchecked hunk (an outline of the
|
|
1555
|
-
packages left to review) plus check/uncheck actions over those chunks,
|
|
1556
|
-
go-to-definition from a `review`-mode hunk line into the file it points at (at
|
|
1557
|
-
its `#line` — a hunk's path is a single whitespace-delimited `./`-relative
|
|
1558
|
-
token, so a hyphen in a filename stays part of the path, never a note
|
|
1559
|
-
separator), symbols over a `qa`-mode file's open questions plus "pick this
|
|
1560
|
-
option"/"uncheck this option" code actions on each option — offered anywhere on
|
|
1561
|
-
the option's list item, including any wrapped continuation lines, not just its
|
|
1562
|
-
own `- [ ]` line — diagnostics for both (live as you edit), and a
|
|
1563
|
-
`gtd.openSteeringFile` command that jumps to the current state's steering file —
|
|
1564
|
-
including `idle`, whose `.gtd/TODO.md` hint gives the one keybinding an answer
|
|
1565
|
-
even before a process has started. The command only names that path — it never
|
|
1566
|
-
stat-checks or creates it — so on a repo that has never run gtd, `.gtd/` may not
|
|
1567
|
-
exist yet and editors differ on opening a file whose parent directory is missing
|
|
1568
|
-
(an empty buffer that creates it on save vs. refusing the path outright); this
|
|
1569
|
-
bites only the very first sketch in a fresh repo, since the workflow's own check
|
|
1570
|
-
scripts create the state directory on the first `gtd next`/`gtd land`.
|
|
1571
|
-
|
|
1572
|
-
It is config-driven via each state's `file:`/`mode:`, and falls back to basename
|
|
1573
|
-
dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and `review` are
|
|
1574
|
-
gtd's built-in steering-file FORMATS, each with its own outline/actions/
|
|
1575
|
-
go-to-definition and a VALIDATOR gtd itself implements; a mode's `format:` and
|
|
1576
|
-
`validate:` are shell commands a workflow (or a project's `.gtdrc`) declares for
|
|
1577
|
-
itself, so you bring your own formatter and your own checkers. Overriding one of
|
|
1578
|
-
`qa`/`review`'s `validate:` with a shell command does NOT lose its outline/
|
|
1579
|
-
actions — those come from the FORMAT (the name), independent of who validates —
|
|
1580
|
-
but `gtd lsp` never runs a shell command per keystroke, so live diagnostics
|
|
1581
|
-
become one `Information` notice pointing at `gtd validate` instead of the
|
|
1582
|
-
built-in findings. Any OTHER mode name — a project's own declared name (e.g.
|
|
1583
|
-
`prose`, for a free-form note) — has no built-in format, so it gets no live
|
|
1584
|
-
editor support at all: `gtd validate`'s emitted script and the `gtd land` gate
|
|
1585
|
-
still format and validate it like any other mode.
|
|
1586
|
-
|
|
1587
|
-
## Development
|
|
1588
|
-
|
|
1589
|
-
```bash
|
|
1590
|
-
npm install
|
|
1591
|
-
npm run dev # run from source, no build (node dev/run.mjs)
|
|
1592
|
-
npm run build # tsdown → dist/gtd.bundle.mjs
|
|
1593
|
-
npm test # the whole gate, via turbo — cached and parallel
|
|
1594
|
-
npx turbo run test:unit # one task, cached (add --force to bypass)
|
|
1595
|
-
npx turbo run test:e2e:live # builds first (turbo dependsOn), then @live
|
|
1596
|
-
npm run test:changed # local pre-flight: only unit/@inmem tests git says changed
|
|
1597
|
-
npm run test:mutation # StrykerJS mutation testing (manual only, ~10 min)
|
|
1598
|
-
npm run typecheck
|
|
1599
|
-
npm run lint
|
|
1600
|
-
```
|
|
1601
|
-
|
|
1602
|
-
`npm test` is a turbo task graph (`turbo.json`): each check declares its own
|
|
1603
|
-
`inputs`, so an unchanged check is skipped, and a check that does run is run in
|
|
1604
|
-
parallel with the others. Caveat from the `test:e2e:live` task's `build`
|
|
1605
|
-
dependency: a bare `npm run test:e2e:live` skips the build, so use
|
|
1606
|
-
`npx turbo run test:e2e:live` to test against a fresh bundle.
|
|
1607
|
-
|
|
1608
|
-
A pre-commit hook is installed automatically via the `prepare` script when you
|
|
1609
|
-
run `npm install` on a fresh clone — it runs
|
|
1610
|
-
[lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
1611
|
-
[oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), mirroring the
|
|
1612
|
-
`format:check` step enforced in CI.
|
|
1613
|
-
|
|
1614
|
-
The decision core is pure and IO-free: the pattern machine's shape (states,
|
|
1615
|
-
patterns, retry) is a plain-data `WorkflowDefinition` (`src/PatternMachine.ts`),
|
|
1616
|
-
and the same module's `resolveState`/`step` are the pure resolver/interpreter
|
|
1617
|
-
over it — so the whole engine is trivially unit-testable in isolation. All
|
|
1618
|
-
git/filesystem/template IO is confined to the edge (`src/Edge.ts`), whose
|
|
1619
|
-
`currentRest` resolves everything derivable from where the process rests right
|
|
1620
|
-
now into a single snapshot, and whose `planStep`/`planEntry` pair separates
|
|
1621
|
-
deciding a transition from performing it — so a command's own capture guards run
|
|
1622
|
-
between the two. The edge reaches the outside world through two content ports
|
|
1623
|
-
(`src/RepoFiles.ts` for working-tree and committed reads, `src/CommandRunner.ts`
|
|
1624
|
-
for the one place gtd spawns a subprocess itself), with the `gtd land` capture
|
|
1625
|
-
guards (`src/StepGuards.ts`) built on top of them.
|
|
1626
|
-
|
|
1627
|
-
`src/testing/` holds the in-memory git/config/filesystem test double every
|
|
1628
|
-
`@inmem` scenario and unit test runs against instead of a real repo — it never
|
|
1629
|
-
ships (a lint rule and a build-time check both enforce that) and is trustworthy
|
|
1630
|
-
only because the same `GitOperations` contract suite runs against a real git
|
|
1631
|
-
repo too.
|
|
76
|
+
gtd itself never executes anything — the driver owns running scripts, and every
|
|
77
|
+
`gtd next` call is strictly mutation-free, safe to poll or peek at any time. See
|
|
78
|
+
[Driving the loop](https://github.com/pmelab/gtd/blob/main/docs/driver.md) for
|
|
79
|
+
the full protocol, including the `message`/`capture` gates and the stalled case
|
|
80
|
+
this sketch leaves out.
|
|
1632
81
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
82
|
+
See [gtd --help](https://github.com/pmelab/gtd/blob/main/docs/cli.md) for the
|
|
83
|
+
full command/flag reference, and
|
|
84
|
+
[Configuration](https://github.com/pmelab/gtd/blob/main/docs/configuration.md)
|
|
85
|
+
for how to inspect or replace the workflow itself.
|
|
1636
86
|
|
|
1637
87
|
## License
|
|
1638
88
|
|