@pmelab/gtd 8.0.0 → 8.2.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 +490 -110
- package/bin/gtd +104 -20
- package/dist/gtd.bundle.mjs +214 -102
- package/package.json +1 -2
- package/schema.json +33 -9
- package/docs/cli.md +0 -675
- package/docs/configuration.md +0 -994
- package/docs/design/configurable-state-machine.md +0 -1439
- package/docs/design/delta-purity-plan.md +0 -283
- package/docs/design/pattern-machine-plan.md +0 -164
- package/docs/design/pluggable-steering-modes.md +0 -154
- package/docs/design/scripted-actor.md +0 -289
- package/docs/design/state-file-association.md +0 -120
- package/docs/design/state-machine-validation.md +0 -437
- package/docs/design/steering-file-loops.md +0 -182
- package/docs/design/steering-file-validation-command.md +0 -385
- package/docs/design/work-packages.md +0 -193
- package/docs/development.md +0 -109
- package/docs/loop.md +0 -219
- package/docs/upgrading.md +0 -293
package/README.md
CHANGED
|
@@ -70,55 +70,27 @@ review and commit the file before your first `gtd step`. `gtd init` takes no
|
|
|
70
70
|
argument and refuses to clobber an existing config; it may also run in a plain
|
|
71
71
|
parent directory (not a git repo) to seed a shared config a nested repo picks
|
|
72
72
|
up. To customize the machine itself, add a `workflow:` key (there is no default
|
|
73
|
-
fallback to merge over — a `workflow:` is the whole definition).
|
|
74
|
-
[Configuration](docs/configuration.md#gtd-init) for the details.
|
|
73
|
+
fallback to merge over — a `workflow:` is the whole definition).
|
|
75
74
|
|
|
76
75
|
## How it works
|
|
77
76
|
|
|
78
77
|
gtd is a small **pattern machine**: named states, each awaiting one actor and
|
|
79
78
|
carrying one piece of content (a script, a prompt, a message, or a squash commit
|
|
80
|
-
template), with an ordered set of change-patterns routing to the next state.
|
|
81
|
-
handful of commands drive it:
|
|
82
|
-
|
|
83
|
-
- **`gtd step <actor>`** — authenticate as `<actor>` and perform the one
|
|
84
|
-
transition the pending changes match.
|
|
85
|
-
- **`gtd next [--json]`** — print whichever actor is awaited and what they
|
|
86
|
-
should do, without mutating anything.
|
|
87
|
-
- **`gtd status`** — a dry-run report of the resolved state and which pattern
|
|
88
|
-
each pending change matches.
|
|
89
|
-
- **`gtd review <commitish>`** — start a brand new review process reviewing
|
|
90
|
-
`<commitish>..HEAD` (e.g. a colleague's PR branch), reusing the workflow's
|
|
91
|
-
existing review/feedback machinery over that diff.
|
|
92
|
-
- **`gtd fix`** — start a brand new process that goes straight into repairing
|
|
93
|
-
the current failing tests, then runs the shared review/squash tail. If the
|
|
94
|
-
suite is already green there is nothing to fix, and the log is left untouched
|
|
95
|
-
— no commit is left behind.
|
|
96
|
-
- **`gtd abandon`** — end the process underway without completing it: rewind to
|
|
97
|
-
the commit it started from, keeping everything it produced as uncommitted
|
|
98
|
-
changes (nothing is discarded).
|
|
99
|
-
- **`gtd restore`** — undo the last squash (or `gtd abandon`) by hard-resetting
|
|
100
|
-
HEAD back to its retained pre-squash tip, bringing the turn-by-turn history
|
|
101
|
-
back. Refuses on a dirty working tree, when there is nothing retained to
|
|
102
|
-
restore, or when HEAD has since moved past it with commits that would be lost.
|
|
103
|
-
- **`gtd visualize`** — serve an interactive diagram of the active workflow on a
|
|
104
|
-
local web server: the main flow (each sub-machine collapsed to one box, with
|
|
105
|
-
its own diagram below — pan/zoom with scroll, drag, or the corner controls),
|
|
106
|
-
per-state details including the state's own raw prompt/message/ script text,
|
|
107
|
-
and a "Current state" panel showing where the active process rests, its
|
|
108
|
-
pending changes, and which action leads where. The panel and diagram highlight
|
|
109
|
-
refresh live (~every 3s) while the page is open, so advancing the process
|
|
110
|
-
elsewhere shows up without a manual refresh. `gtd version` (or
|
|
111
|
-
`gtd --version`/`-v`) prints the installed version and exits; `gtd help` (or
|
|
112
|
-
`gtd --help`/`-h`) prints the command list. Both short-circuit before any repo
|
|
113
|
-
work, so they run anywhere.
|
|
79
|
+
template), with an ordered set of change-patterns routing to the next state.
|
|
114
80
|
|
|
115
81
|
The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
|
|
116
82
|
`"message"` means it's a human's move (stop and hand off); `"script"` means the
|
|
117
83
|
driver runs `content` itself, then steps its actor; `"prompt"` means feed
|
|
118
84
|
`content` to your agent, then run `gtd step <actor>` once it's done. gtd itself
|
|
119
|
-
never executes anything — the driver owns running scripts.
|
|
120
|
-
|
|
121
|
-
|
|
85
|
+
never executes anything — the driver owns running scripts.
|
|
86
|
+
|
|
87
|
+
An `on` edge may also carry a short imperative `action` (e.g. `Accept plan`)
|
|
88
|
+
alongside its existing `describe` sentence — a human-facing name for the choice
|
|
89
|
+
itself, not just the underlying glob pattern. `gtd status` (plain and `--json`)
|
|
90
|
+
and `gtd visualize`'s diagram and inspector all render the two composed
|
|
91
|
+
together: the `action` leads when the edge declares one, with the raw pattern
|
|
92
|
+
(and `describe`) still shown alongside it, so a human choosing between routes
|
|
93
|
+
reads intent first, glob second.
|
|
122
94
|
|
|
123
95
|
The unified workflow has **entry points behind a green-baseline gate, into one
|
|
124
96
|
shared tail**. Every entry first runs your test suite and only starts once it's
|
|
@@ -154,27 +126,154 @@ dropped — a build turn that addresses nothing is refused). Ticking every box
|
|
|
154
126
|
with no comment is the sign-off, which collapses the whole cycle into one commit
|
|
155
127
|
(a **squash finale** whose message an agent drafts). Stepping with a box still
|
|
156
128
|
unticked and no comment is refused (finish reviewing first), as is deleting
|
|
157
|
-
`.gtd/REVIEW.md`.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
and
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
129
|
+
`.gtd/REVIEW.md`.
|
|
130
|
+
|
|
131
|
+
The same review tail also has a direct entry point — `gtd review <commitish>`
|
|
132
|
+
starts a brand new process reviewing `<commitish>..HEAD` with no cycle of its
|
|
133
|
+
own, e.g. a colleague's PR branch. Its squash keeps and describes only the fixes
|
|
134
|
+
made _during_ the review (not the reviewed changeset); a clean sign-off with no
|
|
135
|
+
fixes becomes an empty `chore: human review` commit. A fourth entry, `gtd fix`,
|
|
136
|
+
starts from a clean `idle` and goes straight into repairing a red baseline —
|
|
137
|
+
repair, review, and squash into one commit. If the suite is already green there
|
|
138
|
+
is nothing to fix, and the log is left untouched — no commit is left behind.
|
|
139
|
+
|
|
140
|
+
Every agent state routes its model through two `vars` tiers — `plannerModel`
|
|
141
|
+
(heavier planning and review) and `coderModel` (the coding turns) — so you can
|
|
142
|
+
repoint the models globally in one place (a `vars:` edit or a `GTD_PLANNERMODEL`
|
|
143
|
+
override) instead of per state. Steering-file path vars (`feedbackFile`,
|
|
144
|
+
`reviewFile`, …) work the same way, and propagate to the `on` patterns that
|
|
145
|
+
route on them too — a repointed path var actually reroutes the machine, not just
|
|
146
|
+
the templates that read/write the file. A separate `stateDir` var (default
|
|
147
|
+
`.gtd`) names only where the check scripts keep their own scratch/bookkeeping,
|
|
148
|
+
independent of the per-file vars — relocate one steering file without touching
|
|
149
|
+
it.
|
|
150
|
+
|
|
151
|
+
To inspect or change the machine itself, see [Configuration](#configuration) —
|
|
152
|
+
the workflow is just `.gtdrc` config.
|
|
153
|
+
|
|
154
|
+
## Commands
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Usage: gtd [command] [options]
|
|
158
|
+
|
|
159
|
+
Commands:
|
|
160
|
+
(no command), loop
|
|
161
|
+
Launch the loop driver (bin/gtd), which repeatedly drives
|
|
162
|
+
an agent through gtd next/gtd step calls until the
|
|
163
|
+
workflow rests at a human gate (a non-autonomous state)
|
|
164
|
+
or settles. A bare gtd invocation and gtd loop both
|
|
165
|
+
launch it identically
|
|
166
|
+
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
167
|
+
default variables you are most likely to change (the test
|
|
168
|
+
command) and a Prettier formatting suggestion. gtd runs its
|
|
169
|
+
built-in workflow by default, so no workflow is written —
|
|
170
|
+
add a workflow: key only to customize the machine itself.
|
|
171
|
+
Takes no argument. Run once per repo; refuses if a gtd
|
|
172
|
+
config already exists. Leaves the file uncommitted for you
|
|
173
|
+
to review and commit
|
|
174
|
+
step <actor> Authenticate as <actor>, match the resolved rest's
|
|
175
|
+
declared patterns against the pending changes, and commit
|
|
176
|
+
(or squash) the one resulting transition. Pass
|
|
177
|
+
--cost=<n> (optionally --model=<name>) to record the
|
|
178
|
+
just-finished invocation's token cost and model on the
|
|
179
|
+
turn commit (summed into it.processCost/processCostByModel)
|
|
180
|
+
review <commitish>
|
|
181
|
+
Start a NEW review process at the workflow's declared
|
|
182
|
+
review-entry state (reviewEntry: true), reviewing
|
|
183
|
+
<commitish>..HEAD — e.g. a colleague's PR branch. Requires
|
|
184
|
+
a clean tree resting at the workflow's initial state
|
|
185
|
+
fix Start a NEW process at the workflow's declared fix-entry
|
|
186
|
+
state (fixEntry: true) that goes straight into repairing the
|
|
187
|
+
current failing tests. Requires a clean tree resting at the
|
|
188
|
+
workflow's initial state
|
|
189
|
+
abandon End the process currently underway without completing it:
|
|
190
|
+
close any open review checkout window, then rewind HEAD to
|
|
191
|
+
the commit the process started from, keeping everything it
|
|
192
|
+
produced as uncommitted changes. A no-op when no process is
|
|
193
|
+
underway
|
|
194
|
+
restore Hard-reset HEAD back to the pre-squash tip retained by the
|
|
195
|
+
last squash/abandon (refs/worktree/gtd/history), undoing a
|
|
196
|
+
squash or bringing back an abandoned process's turns.
|
|
197
|
+
Refuses on a dirty working tree, when there is no retained
|
|
198
|
+
history, or when HEAD has advanced past the squash with
|
|
199
|
+
commits that would be lost
|
|
200
|
+
next Print the resolved rest's rendered script/prompt/message
|
|
201
|
+
(no mutation)
|
|
202
|
+
status Print the resolved rest's state/actor and which declared
|
|
203
|
+
pattern (if any) each pending change matches (no mutation)
|
|
204
|
+
validate Format and validate the steering file the resolved rest
|
|
205
|
+
declares, with its mode's commands (its file:/mode:);
|
|
206
|
+
exits non-zero with the findings when it is invalid
|
|
207
|
+
lsp Start the LSP server for .gtd/ steering files (stdio)
|
|
208
|
+
visualize Serve an interactive diagram of the active workflow on a
|
|
209
|
+
local web server (--port <n>, --no-open; --json prints the
|
|
210
|
+
model and exits)
|
|
211
|
+
version Print version and exit
|
|
212
|
+
help Print this help and exit
|
|
213
|
+
|
|
214
|
+
Options:
|
|
215
|
+
--json Output structured JSON instead of plain text
|
|
216
|
+
--port=<n> (gtd visualize only) port to serve on (default: a free port)
|
|
217
|
+
--no-open (gtd visualize only) do not open the browser
|
|
218
|
+
--cost=<n> (gtd step only) record the invocation's token cost
|
|
219
|
+
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
220
|
+
--once (bare gtd or gtd loop only) run exactly one loop beat (one
|
|
221
|
+
human-gate capture, one script check+step, or one agent
|
|
222
|
+
prompt+step), then exit
|
|
223
|
+
--version, -v Print version and exit
|
|
224
|
+
--help, -h Print this help and exit
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`--version` (`-v`) / `gtd version` and `--help` (`-h`) / `gtd help`
|
|
228
|
+
short-circuit before any git or repository-state work — they run outside a repo
|
|
229
|
+
and in any repo state. Bare `gtd` (no subcommand) and `gtd loop` both launch the
|
|
230
|
+
loop driver immediately — neither is a usage error. Any other, truly unknown
|
|
231
|
+
subcommand is a usage error: it prints the help text and exits 1 without
|
|
232
|
+
touching the repository. Every other (recognized) command must be run from the
|
|
233
|
+
**repository root** — gtd derives the workflow, pending changes, and process
|
|
234
|
+
history relative to cwd, so it refuses with a clear error if invoked from a
|
|
235
|
+
subdirectory.
|
|
236
|
+
|
|
237
|
+
`--json`, `--cost=<n>`, and `--model=<name>` (the latter two only for
|
|
238
|
+
`gtd step`) are the only long options the compiled bundle recognizes. Any other
|
|
239
|
+
`--` option (including a typo like `--jsn`) is rejected with a usage error
|
|
240
|
+
rather than silently ignored, so a mistyped flag can never degrade a JSON caller
|
|
241
|
+
to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
|
|
242
|
+
negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
|
|
243
|
+
flag on any command other than `gtd step` are all usage errors.
|
|
244
|
+
|
|
245
|
+
`--once` is a separate, bash-level flag handled entirely by the `bin/gtd` driver
|
|
246
|
+
itself, stripped before anything reaches the bundle.
|
|
247
|
+
|
|
248
|
+
### `gtd status`'s `Next:`/`next`
|
|
249
|
+
|
|
250
|
+
Both plain and `--json` output include a headline preview of what would happen
|
|
251
|
+
next: the first declared `on` edge whose pattern matches the pending changes AS
|
|
252
|
+
A WHOLE (the same first-match-wins semantics `gtd step` itself uses), using its
|
|
253
|
+
`action` when the edge declares one, else its raw pattern, alongside its target
|
|
254
|
+
state. Plain output prints a `Next: <action-or-pattern> → <target>` line (or
|
|
255
|
+
`Next: (no match — nothing would happen)`); `--json`'s `next` key mirrors it as
|
|
256
|
+
`{ action?, pattern, target }`, or `null` on no match.
|
|
257
|
+
|
|
258
|
+
This reports the **declared** route only: a capped `retry` may redirect
|
|
259
|
+
elsewhere at real step time, which `Next:`/`next` does not apply — it previews
|
|
260
|
+
what the declared `on` patterns would match, not a guarantee of where a real
|
|
261
|
+
`gtd step` lands.
|
|
262
|
+
|
|
263
|
+
### Error envelope
|
|
264
|
+
|
|
265
|
+
Every command, in `--json` mode, reports a failure as a machine-readable
|
|
266
|
+
envelope on **stdout**, and still exits 1:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{ "state": "error", "prompt": "<message>" }
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
A human-readable `gtd: <message>` line is still written to **stderr** regardless
|
|
273
|
+
of `--json` — the envelope adds a structured stdout channel, it does not replace
|
|
274
|
+
the plain-text one.
|
|
275
|
+
|
|
276
|
+
## Driving the loop
|
|
178
277
|
|
|
179
278
|
Bare `gtd` (or `gtd loop`) is a ready-to-run driver for the whole protocol —
|
|
180
279
|
point it at a repo and it runs the loop until it's your turn. It drives the
|
|
@@ -182,58 +281,339 @@ autonomous states (agent turns, check runs) and stops at the first
|
|
|
182
281
|
non-autonomous one: reaching a human gate it prints the gate and exits. You act
|
|
183
282
|
by editing files (answer a plan question, tick a review box, fix code) and
|
|
184
283
|
re-launching it — its opening move captures whatever you left, so you never run
|
|
185
|
-
`gtd step human` by hand.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
diagnostics for both (live as you edit), and a `gtd.openSteeringFile` command
|
|
205
|
-
that jumps to the current state's steering file. Config-driven via each state's
|
|
206
|
-
`file:`/`mode:` (see [CLI reference](docs/cli.md#gtd-lsp)) — falls back to
|
|
207
|
-
basename dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and
|
|
208
|
-
`review` are gtd's built-in steering-file MODES with a VALIDATOR gtd itself
|
|
209
|
-
implements; a mode's `format:` and `validate:` are shell commands a workflow (or
|
|
210
|
-
a project's `.gtdrc`) declares for itself, so you bring your own formatter and
|
|
211
|
-
your own checkers (see
|
|
212
|
-
[Configuration](docs/configuration.md#modes--pluggable-steering-file-modes)). A
|
|
213
|
-
third built-in, `prose` — format-only, no validator, used by the simple flow's
|
|
214
|
-
plan file — has no live editor support: `gtd lsp` publishes no symbols or
|
|
215
|
-
diagnostics for it, though `gtd validate` and the `gtd step` gate still format
|
|
216
|
-
and validate it like any other mode. Both halves are enforced by `gtd validate`
|
|
217
|
-
and the `gtd step` gate.
|
|
284
|
+
`gtd step human` by hand.
|
|
285
|
+
|
|
286
|
+
Anything richer at that boundary — opening your editor, desktop notifications,
|
|
287
|
+
terminal-multiplexer status — is the job of an outer wrapper around `gtd`, not
|
|
288
|
+
of the loop itself; see
|
|
289
|
+
[Terminal-multiplexer status: a herdr wrapper](#terminal-multiplexer-status-a-herdr-wrapper)
|
|
290
|
+
below for a worked example. Pass `--once` to restrict a run to exactly one beat
|
|
291
|
+
— one human-gate capture, one check, or one agent turn — instead of driving all
|
|
292
|
+
the way to idle.
|
|
293
|
+
|
|
294
|
+
Bare `gtd` prints one line per event — colored and emoji on a real terminal,
|
|
295
|
+
plain ASCII under `NO_COLOR` or when piped — and redirects the noisier
|
|
296
|
+
agent/check/step subprocess output to a per-repo/per-worktree log file (its path
|
|
297
|
+
is the run's first output line, ready to `tail -f`). Any execution that FAILS
|
|
298
|
+
also replays its last 20 lines of output inline on stderr, on top of the log
|
|
299
|
+
still holding the complete record: an agent turn that fails stops the loop (it
|
|
300
|
+
would fail identically next lap), while a check script that exits non-zero is
|
|
301
|
+
reported as a warning and the loop carries on, because the outcome lives in the
|
|
302
|
+
tree, not in the script's exit code.
|
|
218
303
|
|
|
219
304
|
A workflow state can declare an optional `label:` — a human-readable display
|
|
220
|
-
name surfaced in `gtd next --json`/`gtd status`. The
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
305
|
+
name surfaced in `gtd next --json`/`gtd status`. The driver uses it for its
|
|
306
|
+
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
307
|
+
can use it the same way.
|
|
308
|
+
|
|
309
|
+
### Terminal-multiplexer status: a herdr wrapper
|
|
310
|
+
|
|
311
|
+
[herdr](https://herdr.dev) shows a per-pane status in its sidebar. This wrapper
|
|
312
|
+
reports `gtd`'s own lifecycle to that status without any herdr-specific
|
|
313
|
+
knowledge in `gtd` itself: `working` while the loop drives, `blocked` when it
|
|
314
|
+
comes to rest on a human (a gate, a stall, a non-zero exit), and `idle`
|
|
315
|
+
(rendered as "done" once the pane's tab is unfocused) when a run ends without
|
|
316
|
+
anything owed. It needs nothing from `gtd` beyond `gtd next --json`'s `.actor`
|
|
317
|
+
field, which the loop's driver already reads at every beat.
|
|
318
|
+
|
|
319
|
+
Save this as `~/.local/bin/gtdh`, `chmod +x` it, and run `gtdh` in place of
|
|
320
|
+
`gtd` — it's a plain bash file, so it works from fish or any other shell, and it
|
|
321
|
+
forwards every argument (`gtdh --once`, `gtdh status`, ...).
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
#!/usr/bin/env bash
|
|
325
|
+
# Report gtd's own lifecycle to the herdr pane it runs in: working while the
|
|
326
|
+
# loop drives, blocked when it rests on you, idle (shown as "done") otherwise.
|
|
327
|
+
# Needs nothing from gtd but `gtd next --json`. Outside herdr it is a plain
|
|
328
|
+
# passthrough. Requires `jq` — so does gtd's own loop driver.
|
|
329
|
+
set -uo pipefail
|
|
330
|
+
|
|
331
|
+
pane="${HERDR_PANE_ID:-}"
|
|
332
|
+
|
|
333
|
+
# `--agent gtd` is deliberate: herdr only lets screen detection (or a visible
|
|
334
|
+
# approval prompt) override a reported state when the reported label names a
|
|
335
|
+
# KNOWN agent. "gtd" names none, so the loop's `claude -p` turns can never move
|
|
336
|
+
# this pane's status.
|
|
337
|
+
report() { # report <state>
|
|
338
|
+
[ -n "$pane" ] || return 0
|
|
339
|
+
herdr pane report-agent "$pane" \
|
|
340
|
+
--source custom:gtd --agent gtd --state "$1" >/dev/null 2>&1 || true
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
report working
|
|
344
|
+
trap 'report blocked; exit 130' INT TERM
|
|
345
|
+
|
|
346
|
+
# HERDR_PANE_ID is unset for the loop: herdr's Claude Code hook needs it, and
|
|
347
|
+
# without it no `claude -p` turn reports a claude SESSION identity for this
|
|
348
|
+
# pane. That is load-bearing, not cosmetic — a pane that owns one rejects every
|
|
349
|
+
# later state report, so the report below would be dropped.
|
|
350
|
+
env -u HERDR_PANE_ID gtd "$@"
|
|
351
|
+
rc=$?
|
|
352
|
+
|
|
353
|
+
# Whose turn is it now? `gtd next --json` is mutation-free. A human actor means
|
|
354
|
+
# gtd is waiting on you; anything else means the run ended with nothing owed.
|
|
355
|
+
actor="$(gtd next --json 2>/dev/null | jq -r '.actor // ""' 2>/dev/null || true)"
|
|
356
|
+
|
|
357
|
+
if [ "$rc" -ne 0 ] || [ -z "$actor" ] || [ "$actor" = human ]; then
|
|
358
|
+
report blocked
|
|
359
|
+
else
|
|
360
|
+
report idle
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
exit $rc
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
A few things to know before relying on it:
|
|
367
|
+
|
|
368
|
+
- **Give `gtd` its own pane.** Run an interactive `claude` (or any agent whose
|
|
369
|
+
herdr integration reports session identity) in the same pane and that pane
|
|
370
|
+
owns a `herdr:claude` session until the process exits; while it does, the
|
|
371
|
+
wrapper's reports are silently ignored and the sidebar stops tracking `gtd`.
|
|
372
|
+
- **`blocked` covers the resting `idle` state too** — gtd's own `idle` is a
|
|
373
|
+
human gate (it waits for you to write a steering file), so a finished cycle
|
|
374
|
+
reads as "your turn", which is what it is.
|
|
375
|
+
- The status **persists after the wrapper exits** — that's the point: the
|
|
376
|
+
sidebar keeps showing which worktree is waiting on you. Hand the pane back to
|
|
377
|
+
ordinary detection with
|
|
378
|
+
`herdr pane release-agent "$HERDR_PANE_ID" --source custom:gtd --agent gtd`.
|
|
379
|
+
- A non-zero exit (a stall, a refusal, an error) reports `blocked`: herdr has no
|
|
380
|
+
failure state, and those all need a human.
|
|
381
|
+
- Ctrl-C reports `blocked` rather than leaving a stale `working`.
|
|
382
|
+
|
|
383
|
+
Optionally,
|
|
384
|
+
`herdr pane report-metadata "$HERDR_PANE_ID" --source custom:gtd-display --token summary=<text>`
|
|
385
|
+
sets a value renderable as `$summary` in an Agent sidebar row, if you want more
|
|
386
|
+
than the state itself.
|
|
387
|
+
|
|
388
|
+
## Configuration
|
|
389
|
+
|
|
390
|
+
gtd reads an optional `.gtdrc` config file via
|
|
391
|
+
[cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no `workflow:`
|
|
392
|
+
configured anywhere in the cwd→home config chain, the bundled unified workflow
|
|
393
|
+
is used automatically, so a state command works out of the box with no config at
|
|
394
|
+
all. Supported filenames (searched in this order):
|
|
395
|
+
|
|
396
|
+
- `.gtdrc`
|
|
397
|
+
- `.gtdrc.json`
|
|
398
|
+
- `.gtdrc.yaml`
|
|
399
|
+
- `.gtdrc.yml`
|
|
400
|
+
- `gtd.config.json`
|
|
401
|
+
- `gtd.config.yaml`
|
|
402
|
+
|
|
403
|
+
### Schema
|
|
404
|
+
|
|
405
|
+
`.gtdrc` has exactly three blessed top-level keys:
|
|
406
|
+
|
|
407
|
+
- **`workflow`** (object, optional) — the whole machine definition (its states,
|
|
408
|
+
plus its own `vars:` defaults and `modes:`). Absent = gtd's built-in default
|
|
409
|
+
is used. Declare it to fully REPLACE that default with your own machine (there
|
|
410
|
+
is no `extends`/merge).
|
|
411
|
+
- **`vars`** (object, optional) — a flat `name -> scalar` map, one layer of the
|
|
412
|
+
merged `it.vars` every template sees.
|
|
413
|
+
- **`modes`** (object, optional) — steering-file modes (`format:`/`validate:`
|
|
414
|
+
shell commands), layered over the active workflow's own `modes:` and gtd's
|
|
415
|
+
built-in validators, so a project can plug in its formatter or linter without
|
|
416
|
+
re-declaring that mode on the workflow itself.
|
|
417
|
+
- **`$schema`** (string, optional) — stripped before validation, so it never
|
|
418
|
+
counts as an unknown key. Point it at the published schema for editor-backed
|
|
419
|
+
autocompletion (this is what `gtd init` writes):
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
That URL serves `schema.json` straight out of the published npm tarball, so it
|
|
426
|
+
always matches the latest release. Pin it to a major with
|
|
427
|
+
`@pmelab/gtd@8/schema.json`, or point at your own install
|
|
428
|
+
(`./node_modules/@pmelab/gtd/schema.json`) to work offline.
|
|
429
|
+
|
|
430
|
+
Any other top-level key is **rejected**. The engine blesses no VARIABLE NAMES
|
|
431
|
+
either — `testCommand` is workflow-authored data like any other `it.vars` entry,
|
|
432
|
+
not a special key gtd interprets.
|
|
433
|
+
|
|
434
|
+
### The `workflow:` key
|
|
435
|
+
|
|
436
|
+
A declared `workflow:` key fully REPLACES gtd's built-in default. The built-in
|
|
437
|
+
default is itself a YAML asset (`src/workflows/unified.yaml`) compiled through
|
|
438
|
+
the exact same compiler your own `workflow:` value goes through — no privileged
|
|
439
|
+
code path. Its shape:
|
|
440
|
+
|
|
441
|
+
```yaml
|
|
442
|
+
workflow:
|
|
443
|
+
vars: # optional — the workflow's own declared `it.vars` defaults
|
|
444
|
+
anyKey: anyScalarValue
|
|
445
|
+
modes: # optional — steering-file modes a state's `mode:` may name
|
|
446
|
+
<name>:
|
|
447
|
+
format: <shell command> # at least one of format/validate
|
|
448
|
+
validate: <shell command>
|
|
449
|
+
states:
|
|
450
|
+
<name>:
|
|
451
|
+
actor: <string> # forbidden on a commit state, required otherwise
|
|
452
|
+
script: <string> # exactly one of script/prompt/message/commit
|
|
453
|
+
prompt: <string>
|
|
454
|
+
message: <string>
|
|
455
|
+
commit: <string>
|
|
456
|
+
on: # a mapping, DECLARATION ORDER PRESERVED
|
|
457
|
+
"<pattern>": <targetState> # short form
|
|
458
|
+
"<pattern>": {
|
|
459
|
+
to: <targetState>,
|
|
460
|
+
describe: <sentence>,
|
|
461
|
+
action: <label>,
|
|
462
|
+
} # description/action
|
|
463
|
+
initial: true # exactly one state across the whole workflow
|
|
464
|
+
retry:
|
|
465
|
+
max: <number>
|
|
466
|
+
otherwise: <targetState>
|
|
467
|
+
model: <string> # optional, opaque harness hint — forbidden on a commit state
|
|
468
|
+
memory: <string> # optional, opaque memory-scope label — forbidden on a commit state
|
|
469
|
+
file: <string> # optional, an Eta template naming the state's steering file
|
|
470
|
+
mode: <modeName> # optional, requires "file" — a built-in (qa/review/prose) or a `modes:` entry
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Authoring or editing a workflow with a coding agent? `skills/authoring/SKILL.md`
|
|
474
|
+
is the agent-facing contract for producing a valid `workflow:` — the state
|
|
475
|
+
model, pattern grammar, load-time rules, and how to verify a change compiles.
|
|
476
|
+
|
|
477
|
+
### Variables
|
|
478
|
+
|
|
479
|
+
Every template — `script`/`prompt`/`message`/`commit`, and
|
|
480
|
+
`model`/`memory`/`file` — sees `it.vars`: a flat `Record<string, string>`
|
|
481
|
+
assembled from three layers, **later wins**:
|
|
482
|
+
|
|
483
|
+
1. **The workflow's own `vars:` key** (sibling to `states:`) — the workflow
|
|
484
|
+
author's declared defaults. The unified template declares
|
|
485
|
+
`vars: { testCommand: "npm test" }`, read by `checking`'s script as
|
|
486
|
+
`<%~ it.vars.testCommand %>`.
|
|
487
|
+
2. **A top-level `.gtdrc` `vars:` key** (a sibling of `workflow:`, NOT nested
|
|
488
|
+
inside it) — per-repo tuning without redefining the whole workflow.
|
|
489
|
+
3. **`GTD_<UPPERCASE-name>` environment variables** — highest precedence,
|
|
490
|
+
checked at every invocation, case-insensitively against each name already
|
|
491
|
+
declared by layer 1 or 2: `GTD_TESTCOMMAND` overrides `testCommand`. The
|
|
492
|
+
environment can only OVERRIDE a name a config layer already declared — a
|
|
493
|
+
`GTD_*` var matching no declared name is silently ignored.
|
|
494
|
+
|
|
495
|
+
Values in layers 1–2 must be YAML scalars (string/number/boolean), coerced to
|
|
496
|
+
strings at load time; an object or array value is a load error.
|
|
497
|
+
|
|
498
|
+
```yaml
|
|
499
|
+
# .gtdrc — overriding the unified template's testCommand
|
|
500
|
+
vars:
|
|
501
|
+
testCommand: npm run test:ci
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# highest precedence — beats both the workflow default and the .gtdrc value above
|
|
506
|
+
GTD_TESTCOMMAND="npm run test -- --bail" gtd next
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Lookup and precedence
|
|
510
|
+
|
|
511
|
+
gtd walks from the current working directory **up to your home directory** (or
|
|
512
|
+
to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
|
|
513
|
+
finds along the way. All found levels are **deep-merged**, with the **innermost
|
|
514
|
+
(cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
|
|
515
|
+
directory cascades to every checkout beneath it, while any individual checkout
|
|
516
|
+
can still override with its own `.gtdrc`.
|
|
517
|
+
|
|
518
|
+
### Validation and errors
|
|
519
|
+
|
|
520
|
+
Config-shape problems (unknown keys, wrong types, unreadable file references)
|
|
521
|
+
are collected together; if the shape is clean, the assembled definition is
|
|
522
|
+
additionally run through the engine's own validation. A bad config throws
|
|
523
|
+
**one** error listing every finding, at load time — before anything touches the
|
|
524
|
+
repository — never partially, and never deferred to step time:
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
workflow config:
|
|
528
|
+
- state "idle": must declare exactly one of script/prompt/message/commit (found 2)
|
|
529
|
+
- state "idle": "on" target "nowhere" is not a defined state
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Those findings include the **semantic graph checks**: every `on` target and
|
|
533
|
+
`retry.otherwise` must name a defined state, and every state must be
|
|
534
|
+
**reachable** from the initial state. All load failures exit **1** and write to
|
|
535
|
+
**stderr**, never stdout.
|
|
536
|
+
|
|
537
|
+
Many of these problems never reach gtd at all if your editor validates against
|
|
538
|
+
the [published schema](#schema), which fully types the `workflow:` key. The
|
|
539
|
+
rules JSON Schema cannot express — exactly one content kind, exactly one
|
|
540
|
+
`initial: true`, targets naming defined states, reachability — remain the
|
|
541
|
+
compiler's job at load time.
|
|
542
|
+
|
|
543
|
+
## Repository requirements
|
|
544
|
+
|
|
545
|
+
- **Single writer, linear branch.** A process's history is walked via
|
|
546
|
+
**first-parent** commits only.
|
|
547
|
+
- **Test/build artifacts must be gitignored.** This is **load-bearing**, not a
|
|
548
|
+
style preference: every step decision detects "clean" via
|
|
549
|
+
`git diff --name-status HEAD` (tracked changes) unioned with
|
|
550
|
+
`git ls-files --others --exclude-standard` (untracked files), which silently
|
|
551
|
+
omits anything matched by `.gitignore`. If a `script` state's command (or the
|
|
552
|
+
build it triggers) writes output — a `dist/`, a coverage report, a log file —
|
|
553
|
+
into the working tree, the tree never goes clean after a green run, and the
|
|
554
|
+
check's `"C"` pattern never fires. Gitignore every path your scripts write
|
|
555
|
+
before wiring gtd into a repo.
|
|
556
|
+
- **Repository root invocation.** Every state subcommand must run from the git
|
|
557
|
+
repository root. `--help`/`--version` (and the `help`/`version` subcommands),
|
|
558
|
+
`lsp`, and `visualize` skip this guard entirely (`visualize` still reads the
|
|
559
|
+
`.gtdrc` workflow, but needs no git state).
|
|
560
|
+
- **Linked worktrees are independent.** N `git worktree` worktrees of one
|
|
561
|
+
repository (sharing a single `.git`) each run their own gtd process: state is
|
|
562
|
+
derived from that worktree's own HEAD, and the review checkout window's refs
|
|
563
|
+
live in git's per-worktree `refs/worktree/gtd/*` namespace, so a review
|
|
564
|
+
resting in one worktree neither blocks nor rewrites any other.
|
|
565
|
+
|
|
566
|
+
## Editor integration
|
|
567
|
+
|
|
568
|
+
`gtd lsp` starts an LSP server over stdio for `.gtd/` steering files — a symbol
|
|
569
|
+
per `review`-mode chunk that still has an unchecked hunk (an outline of the
|
|
570
|
+
packages left to review) plus check/uncheck actions over those chunks,
|
|
571
|
+
go-to-definition from a `review`-mode hunk line into the file it points at (at
|
|
572
|
+
its `#line`), symbols over a `qa`-mode file's open questions plus "pick this
|
|
573
|
+
option"/"uncheck this option" code actions on each option — offered anywhere on
|
|
574
|
+
the option's list item, including any wrapped continuation lines, not just its
|
|
575
|
+
own `- [ ]` line — diagnostics for both (live as you edit), and a
|
|
576
|
+
`gtd.openSteeringFile` command that jumps to the current state's steering file.
|
|
577
|
+
|
|
578
|
+
It is config-driven via each state's `file:`/`mode:`, and falls back to basename
|
|
579
|
+
dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and `review` are
|
|
580
|
+
gtd's built-in steering-file MODES with a VALIDATOR gtd itself implements; a
|
|
581
|
+
mode's `format:` and `validate:` are shell commands a workflow (or a project's
|
|
582
|
+
`.gtdrc`) declares for itself, so you bring your own formatter and your own
|
|
583
|
+
checkers. A third built-in, `prose` — format-only, no validator, used by the
|
|
584
|
+
simple flow's plan file — has no live editor support: `gtd lsp` publishes no
|
|
585
|
+
symbols or diagnostics for it, though `gtd validate` and the `gtd step` gate
|
|
586
|
+
still format and validate it like any other mode.
|
|
587
|
+
|
|
588
|
+
## Development
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
npm install
|
|
592
|
+
npm run dev # run from source, no build (node dev/run.mjs)
|
|
593
|
+
npm run build # tsdown → dist/gtd.bundle.mjs
|
|
594
|
+
npm test # format:check, typecheck, lint, unit + e2e tests, fallow
|
|
595
|
+
npm run test:unit # vitest unit tests (the pure resolver) — --project unit
|
|
596
|
+
npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
|
|
597
|
+
npm run test:mutation # StrykerJS mutation testing (manual only, ~10 min)
|
|
598
|
+
npm run typecheck
|
|
599
|
+
npm run lint
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
A pre-commit hook is installed automatically via the `prepare` script when you
|
|
603
|
+
run `npm install` on a fresh clone — it runs
|
|
604
|
+
[lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
605
|
+
[oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), mirroring the
|
|
606
|
+
`format:check` step enforced in CI.
|
|
607
|
+
|
|
608
|
+
The decision core is pure and IO-free: the pattern machine's shape (states,
|
|
609
|
+
patterns, retry) is a plain-data `WorkflowDefinition` (`src/PatternMachine.ts`),
|
|
610
|
+
and the same module's `resolveState`/`step` are the pure resolver/interpreter
|
|
611
|
+
over it — so the whole engine is trivially unit-testable in isolation. All
|
|
612
|
+
git/filesystem/template IO is confined to the edge (`src/Edge.ts`).
|
|
613
|
+
|
|
614
|
+
Releases are automatic: push releasable Conventional Commits (`fix:`, `feat:`,
|
|
615
|
+
or breaking changes) to `main` and semantic-release computes the next version,
|
|
616
|
+
builds the bundle, tags it, and publishes.
|
|
237
617
|
|
|
238
618
|
## License
|
|
239
619
|
|