@pmelab/gtd 8.3.1 → 8.5.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 +320 -118
- package/bin/gtd +80 -29
- package/dist/gtd.bundle.mjs +4684 -3247
- package/package.json +11 -9
- package/schema.json +147 -265
package/README.md
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
**Chat is a terrible source of truth. Git isn't.**
|
|
7
7
|
|
|
8
8
|
**gtd** is a git-aware CLI that derives the entire agentic workflow — capture,
|
|
9
|
-
plan, build,
|
|
10
|
-
prompt for whatever agent you point at it. Every
|
|
11
|
-
|
|
12
|
-
homework.
|
|
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. The test
|
|
11
|
+
command is printed for your driver to run, and the branch is decided by what the
|
|
12
|
+
run leaves in the tree — so the agent never grades its own homework.
|
|
13
13
|
|
|
14
14
|
No chat scrollback. No lost sessions. No infinite fix loops. Just git.
|
|
15
15
|
|
|
@@ -95,8 +95,8 @@ reads intent first, glob second.
|
|
|
95
95
|
The unified workflow has **entry points behind a green-baseline gate, into one
|
|
96
96
|
shared tail**. Every entry first runs your test suite and only starts once it's
|
|
97
97
|
green — you never build (or review) on top of a red baseline; a red run halts
|
|
98
|
-
and tells you to repair it first (that's what `gtd fix` is
|
|
99
|
-
steering-file entries are chosen by which file you create:
|
|
98
|
+
and tells you to repair it first (that's what `gtd --entry fix-precheck` is
|
|
99
|
+
for). The two steering-file entries are chosen by which file you create:
|
|
100
100
|
|
|
101
101
|
- Create **`.gtd/TODO.md`** with a short sketch to start the **simple** flow: an
|
|
102
102
|
agent develops your sketch into a concrete plan — deciding open points itself
|
|
@@ -105,44 +105,66 @@ steering-file entries are chosen by which file you create:
|
|
|
105
105
|
runs your tests (looping on failures) — the check run also mechanically sweeps
|
|
106
106
|
`.gtd/TODO.md` and other spent steering files, so a plan that's left in place
|
|
107
107
|
by mistake never leaks into the review diff or the final squash.
|
|
108
|
-
- Create **`.gtd/REQUIREMENTS.md`** to start the **advanced** flow:
|
|
109
|
-
product then technical Q&A (`.gtd/REQUIREMENTS.md` →
|
|
110
|
-
each open question offers a couple
|
|
111
|
-
`- [ ] _your answer_` slot, and you tick exactly
|
|
112
|
-
won't let a phase advance while any question is
|
|
113
|
-
decomposition into work **packages** (each a set of
|
|
114
|
-
build turn fans out to parallel subagents), a
|
|
115
|
-
per-package **agentic review** that verifies the
|
|
108
|
+
- Create **`.gtd/REQUIREMENTS.md`** to start the **advanced** flow: one design
|
|
109
|
+
conversation across product then technical Q&A (`.gtd/REQUIREMENTS.md` →
|
|
110
|
+
`.gtd/ARCHITECTURE.md`) and decomposition — each open question offers a couple
|
|
111
|
+
of candidate answers plus a `- [ ] _your answer_` slot, and you tick exactly
|
|
112
|
+
one per question (the gate won't let a phase advance while any question is
|
|
113
|
+
unanswered) — then decomposition into work **packages** (each a set of
|
|
114
|
+
independent tasks a single build turn fans out to parallel subagents), a
|
|
115
|
+
per-package test loop, and a per-package **agentic review** that verifies the
|
|
116
|
+
package against its spec. All three phases share one machine identity, so the
|
|
117
|
+
agent never re-explores the codebase from scratch between them, and the
|
|
118
|
+
human's answers carry forward into decomposition.
|
|
116
119
|
|
|
117
120
|
Both flows converge on the same tail: an agent hands you a `.gtd/REVIEW.md`
|
|
118
121
|
checkbox review of the diff — the prompt never inlines the diff itself; it names
|
|
119
122
|
the commit the changes are based at and the agent runs `git diff` to read the
|
|
120
|
-
range before writing the review. While the
|
|
121
|
-
**review checkout window**: HEAD is rewound to the review base with the
|
|
122
|
-
tree untouched, so the whole reviewable change shows up as ordinary
|
|
123
|
-
changes in your editor's normal git integration (and files added
|
|
124
|
-
|
|
125
|
-
window closes on the next gtd invocation. Tick a box as you
|
|
126
|
-
(ticking just records "I read this"), and leave a **comment**
|
|
127
|
-
changes: a note on a line, an inline `// TODO`-style comment in the
|
|
128
|
-
direct code edit. Any comment sends a build + re-review round — an
|
|
129
|
-
turns your comments into an explicit instruction list, then a build
|
|
130
|
-
implements it (a re-review then covers only the follow-through, and a
|
|
131
|
-
is treated as your own fix the agent completes without reverting your
|
|
132
|
-
comment can't be silently dropped — a build turn that addresses nothing
|
|
133
|
-
refused).
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
123
|
+
range before writing the review. While the process rests at that gate, gtd opens
|
|
124
|
+
a **review checkout window**: HEAD is rewound to the review base with the
|
|
125
|
+
working tree untouched, so the whole reviewable change shows up as ordinary
|
|
126
|
+
uncommitted changes in your editor's normal git integration (and files added
|
|
127
|
+
during the process show up as ordinary untracked files, so discarding one
|
|
128
|
+
deletes it). The window closes on the next gtd invocation. Tick a box as you
|
|
129
|
+
review each hunk (ticking just records "I read this"), and leave a **comment**
|
|
130
|
+
to request changes: a note on a line, an inline `// TODO`-style comment in the
|
|
131
|
+
code, or a direct code edit. Any comment sends a build + re-review round — an
|
|
132
|
+
agent first turns your comments into an explicit instruction list, then a build
|
|
133
|
+
turn implements it (a re-review then covers only the follow-through, and a
|
|
134
|
+
hand-edit is treated as your own fix the agent completes without reverting your
|
|
135
|
+
lines; a comment can't be silently dropped — a build turn that addresses nothing
|
|
136
|
+
is refused). For the simple flow, the build turn that follows through on
|
|
137
|
+
feedback and the turn that drafts the final squash message both resume the same
|
|
138
|
+
session that built the feature in the first place, since the review tail is
|
|
139
|
+
nested inside that build identity rather than sitting beside it. Ticking every
|
|
140
|
+
box with no comment is the sign-off, which collapses the whole process into one
|
|
141
|
+
commit (a **squash finale** whose message an agent drafts). Stepping with a box
|
|
142
|
+
still unticked and no comment is refused (finish reviewing first), as is
|
|
143
|
+
deleting `.gtd/REVIEW.md`.
|
|
144
|
+
|
|
145
|
+
The same review tail also has a direct entry point —
|
|
146
|
+
`gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
|
|
147
|
+
process reviewing `<commitish>..HEAD` with no build of its own, e.g. a
|
|
148
|
+
colleague's PR branch (`review-gate.check`'s `reviewBase:` is a template bound
|
|
149
|
+
to the `reviewBase` var, so supplying it via `--var` fixes the whole process's
|
|
150
|
+
diff base to that commitish). Its squash keeps and describes only the fixes made
|
|
151
|
+
_during_ the review (not the reviewed changeset); a clean sign-off with no fixes
|
|
152
|
+
becomes an empty `chore: human review` commit. A fourth entry,
|
|
153
|
+
`gtd --entry fix-precheck`, starts from a clean `idle` and goes straight into
|
|
154
|
+
repairing a red baseline — repair, review, and squash into one commit. If the
|
|
155
|
+
suite is already green there is nothing to fix, and the log is left untouched —
|
|
156
|
+
no commit is left behind.
|
|
157
|
+
|
|
158
|
+
`--entry` itself isn't limited to states flagged `entry: true` — it accepts
|
|
159
|
+
**any** declared, non-commit state of the active workflow (see
|
|
160
|
+
[`gtd step`](#commands)/`gtd --entry` below). `entry: true` only marks a state
|
|
161
|
+
as an _extra_ reachability root (and drives a badge in `gtd visualize`) for a
|
|
162
|
+
state that would otherwise be unreachable from the ordinary `idle` rest —
|
|
163
|
+
`review-gate.check` and `fix-precheck` need it for exactly that reason, while
|
|
164
|
+
`plan-gate.check`/`spec-gate.check` carry it too (the bundled template dedups
|
|
165
|
+
the three `entryGate` instances into one shared machine, so flagging the shared
|
|
166
|
+
state flags all three) even though `idle` already reaches them the ordinary way.
|
|
167
|
+
`entry: true` is not a precondition for `--entry` to target a state.
|
|
146
168
|
|
|
147
169
|
Every agent state routes its model through two `vars` tiers — `plannerModel`
|
|
148
170
|
(heavier planning and review) and `coderModel` (the coding turns) — so you can
|
|
@@ -183,16 +205,14 @@ Commands:
|
|
|
183
205
|
(or squash) the one resulting transition. Pass
|
|
184
206
|
--cost=<n> (optionally --model=<name>) to record the
|
|
185
207
|
just-finished invocation's token cost and model on the
|
|
186
|
-
turn commit (summed into it.processCost/processCostByModel)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
current failing tests. Requires a clean tree resting at the
|
|
195
|
-
workflow's initial state
|
|
208
|
+
turn commit (summed into it.processCost/processCostByModel).
|
|
209
|
+
Pass --entry <state> to start a brand NEW process at
|
|
210
|
+
<state> instead — any declared, non-commit state — with
|
|
211
|
+
repeatable --var <name>=<value> supplying that new
|
|
212
|
+
process's fixed it.vars overrides
|
|
213
|
+
(no command) --entry <state>
|
|
214
|
+
Short form of 'step human --entry <state>' — starts a new
|
|
215
|
+
process authenticated as human, e.g. 'gtd --entry <state>'
|
|
196
216
|
abandon End the process currently underway without completing it:
|
|
197
217
|
close any open review checkout window, then rewind HEAD to
|
|
198
218
|
the commit the process started from, keeping everything it
|
|
@@ -204,8 +224,7 @@ Commands:
|
|
|
204
224
|
Refuses on a dirty working tree, when there is no retained
|
|
205
225
|
history, or when HEAD has advanced past the squash with
|
|
206
226
|
commits that would be lost
|
|
207
|
-
next Print the resolved rest's rendered script/prompt/message
|
|
208
|
-
(no mutation)
|
|
227
|
+
next Print the resolved rest's rendered script/prompt/message (no mutation)
|
|
209
228
|
status Print the resolved rest's state/actor and which declared
|
|
210
229
|
pattern (if any) each pending change matches (no mutation)
|
|
211
230
|
validate Format and validate the steering file the resolved rest
|
|
@@ -224,9 +243,15 @@ Options:
|
|
|
224
243
|
--no-open (gtd visualize only) do not open the browser
|
|
225
244
|
--cost=<n> (gtd step only) record the invocation's token cost
|
|
226
245
|
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
227
|
-
--
|
|
228
|
-
|
|
229
|
-
|
|
246
|
+
--entry <state> (gtd step, or with no command at all) start a brand new
|
|
247
|
+
process at <state> — any declared, non-commit state —
|
|
248
|
+
instead of stepping the one currently resting. Not
|
|
249
|
+
combinable with --cost/--model (an entry is not a metered
|
|
250
|
+
agent turn)
|
|
251
|
+
--var <name>=<value>
|
|
252
|
+
(with --entry; repeatable) supply a fixed it.vars
|
|
253
|
+
override for the new process; the name must already be
|
|
254
|
+
declared by the workflow's own vars: or the .gtdrc vars:
|
|
230
255
|
--version, -v Print version and exit
|
|
231
256
|
--help, -h Print this help and exit
|
|
232
257
|
```
|
|
@@ -241,13 +266,18 @@ touching the repository. Every other (recognized) command must be run from the
|
|
|
241
266
|
history relative to cwd, so it refuses with a clear error if invoked from a
|
|
242
267
|
subdirectory.
|
|
243
268
|
|
|
244
|
-
`--json`, `--cost=<n>`,
|
|
245
|
-
`gtd step`
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
flag
|
|
269
|
+
`--json`, `--cost=<n>`, `--model=<name>` (the latter two only for `gtd step`),
|
|
270
|
+
`--entry <state>` (`gtd step` or no command at all), and `--var <name>=<value>`
|
|
271
|
+
(with `--entry`, repeatable) are the only long options the compiled bundle
|
|
272
|
+
recognizes. `--entry`/`--var` accept both the `--flag=value` and the
|
|
273
|
+
space-separated `--flag value` form. Any other `--` option (including a typo
|
|
274
|
+
like `--jsn`) is rejected with a usage error rather than silently ignored, so a
|
|
275
|
+
mistyped flag can never degrade a JSON caller to plain-text mode. `--var` with
|
|
276
|
+
no `--entry`, a duplicate `--var` name, or `--cost`/`--model` combined with
|
|
277
|
+
`--entry` are all usage errors too. A bare `--cost`/`--model` with no value, a
|
|
278
|
+
non-numeric or negative `--cost`, an empty `--model`, `--model` without
|
|
279
|
+
`--cost`, or either flag on any command other than `gtd step` are all usage
|
|
280
|
+
errors.
|
|
251
281
|
|
|
252
282
|
`--once` is a separate, bash-level flag handled entirely by the `bin/gtd` driver
|
|
253
283
|
itself, stripped before anything reaches the bundle.
|
|
@@ -276,9 +306,16 @@ envelope on **stdout**, and still exits 1:
|
|
|
276
306
|
{ "state": "error", "prompt": "<message>" }
|
|
277
307
|
```
|
|
278
308
|
|
|
309
|
+
This covers every failure mode, not just a command's own refusal: a **usage
|
|
310
|
+
error** (an unknown flag, a missing argument, `gtd --entry version`'s "not an
|
|
311
|
+
enterable state") and a **defect** (a layer throwing outside the ordinary error
|
|
312
|
+
channel) both get the same envelope — there is no failure path that reaches
|
|
313
|
+
`--json` without one.
|
|
314
|
+
|
|
279
315
|
A human-readable `gtd: <message>` line is still written to **stderr** regardless
|
|
280
316
|
of `--json` — the envelope adds a structured stdout channel, it does not replace
|
|
281
|
-
the plain-text one.
|
|
317
|
+
the plain-text one. Stderr always carries exactly one `gtd: ` prefix: a message
|
|
318
|
+
already authored with its own `gtd:`/`gtd <cmd>:` prefix is never doubled.
|
|
282
319
|
|
|
283
320
|
## Driving the loop
|
|
284
321
|
|
|
@@ -313,12 +350,45 @@ name surfaced in `gtd next --json`/`gtd status`. The driver uses it for its
|
|
|
313
350
|
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
314
351
|
can use it the same way.
|
|
315
352
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
353
|
+
Memory is **entry-scoped to a machine**, not a state-authored label: each
|
|
354
|
+
machine instance (a node in the `machines:` tree, e.g. `build`, `build.health`,
|
|
355
|
+
`packages.item`, `packages.item.health`) owns its own conversational scope, and
|
|
356
|
+
a `prompt`-content state's `memory` key — surfaced in `gtd next --json`/
|
|
357
|
+
`gtd status --json`'s `memory` field, and as a `Memory: <key>` line in plain
|
|
358
|
+
`gtd status` — is computed, never authored, as `<scope>#<hash7>`: `<scope>` is
|
|
359
|
+
that machine instance's dotted path (the root instance is shown as `root`), and
|
|
360
|
+
`<hash7>` anchors to the commit the CURRENT unbroken entry into that scope
|
|
361
|
+
started FROM. Entering a **descendant** scope (e.g. dipping from `build` into
|
|
362
|
+
`build.health`) does not break the parent's unbroken run — a full agent turn in
|
|
363
|
+
a nested child machine, then back to the parent, still resumes the SAME parent
|
|
364
|
+
conversation; entering a **sibling or unrelated** scope does start a fresh one.
|
|
365
|
+
The bundled template's `build.review` (the human review tail) is a worked
|
|
366
|
+
example: it is nested INSIDE `build` (the builder's own machine) precisely
|
|
367
|
+
because that descendant relationship is what lets `build.addressing` and
|
|
368
|
+
`build.squashing` resume the session that built the feature across a full review
|
|
369
|
+
round-trip, instead of a root-level sibling breaking that run on every pass
|
|
370
|
+
through the tail. Two instances of the same reusable machine (e.g.
|
|
371
|
+
`build.health` and `packages.item.health`, both instantiating `healthGate`) get
|
|
372
|
+
different scopes and so never share a key, even though they're the "same shaped"
|
|
373
|
+
machine. One consequence is a structural guarantee: **a reviewer's turn never
|
|
374
|
+
resumes an implementer's session, and vice versa** — a reviewer machine and the
|
|
375
|
+
implementer machine it reviews are always different instances with different
|
|
376
|
+
scopes.
|
|
377
|
+
|
|
378
|
+
The loop driver (`bin/gtd`) tracks this as a per-scope **session table** — one
|
|
379
|
+
`<key> <session_id>` row per scope — persisted at `$gitdir/gtd-loop-memory` (the
|
|
380
|
+
git dir, never the working tree, so `gtd status` and the pending diff never see
|
|
381
|
+
it), not a single "last" value: this is what lets a scope's session survive an
|
|
382
|
+
excursion into a child machine's own agent turn (the child writes its own row;
|
|
383
|
+
the parent's row is untouched). On each agent-prompt turn, the driver looks the
|
|
384
|
+
current key up by exact string match against the table (a hit resumes that row's
|
|
385
|
+
session, a miss starts fresh) and, on write, replaces only the ONE row whose
|
|
386
|
+
scope matches the current key's scope — every other scope's row is left as-is.
|
|
387
|
+
This is exported to the agent adapter as `$GTD_LOOP_MEMORY` (the key),
|
|
388
|
+
`$GTD_LOOP_SESSION_ID`, and `$GTD_LOOP_MEMORY_RESUME` (`"1"` when resuming). If
|
|
389
|
+
the remembered session no longer exists (retention expired, `~/.claude/projects`
|
|
390
|
+
wiped, a machine change), the driver degrades to a fresh session with a warning
|
|
391
|
+
instead of stopping the loop.
|
|
322
392
|
|
|
323
393
|
### Terminal-multiplexer status: a herdr wrapper
|
|
324
394
|
|
|
@@ -384,7 +454,7 @@ A few things to know before relying on it:
|
|
|
384
454
|
owns a `herdr:claude` session until the process exits; while it does, the
|
|
385
455
|
wrapper's reports are silently ignored and the sidebar stops tracking `gtd`.
|
|
386
456
|
- **`blocked` covers the resting `idle` state too** — gtd's own `idle` is a
|
|
387
|
-
human gate (it waits for you to write a steering file), so a finished
|
|
457
|
+
human gate (it waits for you to write a steering file), so a finished process
|
|
388
458
|
reads as "your turn", which is what it is.
|
|
389
459
|
- The status **persists after the wrapper exits** — that's the point: the
|
|
390
460
|
sidebar keeps showing which worktree is waiting on you. Hand the pane back to
|
|
@@ -458,40 +528,75 @@ workflow:
|
|
|
458
528
|
anyKey: anyScalarValue
|
|
459
529
|
modes: # optional — steering-file modes a state's `mode:` may name
|
|
460
530
|
<name>:
|
|
461
|
-
format: <shell command> #
|
|
531
|
+
format: <shell command> # both optional — {} is the format-only tier
|
|
462
532
|
validate: <shell command>
|
|
463
|
-
|
|
533
|
+
entry:
|
|
534
|
+
default: <machine name> # which machine is the ROOT instance
|
|
535
|
+
machines:
|
|
464
536
|
<name>:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
537
|
+
model: <string> # optional, opaque harness hint — stamped onto every one of THIS machine's own `prompt` states; declared ONCE per machine, never per state
|
|
538
|
+
params: [<param>, ...] # optional, advisory — documents which $params a caller may bind
|
|
539
|
+
entry: <local or ref key> # this machine's own default local, resolved recursively
|
|
540
|
+
states:
|
|
541
|
+
<local>:
|
|
542
|
+
actor: <string> # forbidden on a commit state, required otherwise
|
|
543
|
+
script: <string> # exactly one of script/prompt/message/commit
|
|
544
|
+
prompt: <string>
|
|
545
|
+
message: <string>
|
|
546
|
+
commit: <string>
|
|
547
|
+
on: # a mapping, DECLARATION ORDER PRESERVED
|
|
548
|
+
"<pattern>": <targetState> # short form
|
|
549
|
+
"<pattern>": {
|
|
550
|
+
to: <targetState>,
|
|
551
|
+
describe: <sentence>,
|
|
552
|
+
action: <label>,
|
|
553
|
+
} # description/action
|
|
554
|
+
retry:
|
|
555
|
+
max: <number>
|
|
556
|
+
otherwise: <targetState>
|
|
557
|
+
label: <string> # optional, opaque display name passed through `gtd next --json`/`gtd status --json`
|
|
558
|
+
file: <string> # optional, an Eta template naming the state's steering file
|
|
559
|
+
mode: <modeName> # optional, requires "file" — must be declared in `modes:` (qa/review are seeded for you; everything else, including prose, you declare)
|
|
560
|
+
reviewWindow: true # optional — open the review checkout window at rest here
|
|
561
|
+
reviewBase: true # optional — anchor the review window's diff base to this state's most-recent commit
|
|
562
|
+
# reviewBase: <Eta template> # OR a template — rendered (only meaningful entering via --entry) to a commitish that fixes the WHOLE PROCESS's diff base
|
|
563
|
+
requireProgress: true # optional, requires "file" — refuse a turn whose only change deletes this state's own `file:`
|
|
564
|
+
answerGate: true # optional, requires "file" — refuse a turn until every open question in the (qa-mode) `file:` is answered
|
|
565
|
+
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)
|
|
566
|
+
<local>: { machine: <name>, with: { <param>: <value> } } # a REFERENCE — instantiates <name> as a child, qualified as `<local>.<childLocal>`
|
|
485
567
|
```
|
|
486
568
|
|
|
569
|
+
There is no `memory:` key anywhere in this shape — a state's memory scope is
|
|
570
|
+
never authored, only computed from its position in the machine tree (see
|
|
571
|
+
[Driving the loop](#driving-the-loop) above for the key format and the driver's
|
|
572
|
+
per-scope session table).
|
|
573
|
+
|
|
574
|
+
The top-level `entry:` key (naming the root machine, `entry.default`) and a
|
|
575
|
+
state's own `entry: true` flag are the same word at two different levels, by
|
|
576
|
+
design: one selects the workflow's root machine, the other opts one state in as
|
|
577
|
+
an extra manual entry point.
|
|
578
|
+
|
|
579
|
+
A workflow is authored as a TREE of reusable, parameterized machines — a
|
|
580
|
+
gate/loop written once and instantiated several times with different `with:`
|
|
581
|
+
bindings (dedup), or a complex cluster grouped under one name for source
|
|
582
|
+
comprehension (encapsulation). Every reference is expanded at load time into
|
|
583
|
+
concrete, qualified states (`<local>.<childLocal>`, however deep) before the
|
|
584
|
+
engine ever sees the definition — see `src/Machines.ts` for the mechanism.
|
|
585
|
+
MACHINE BOUNDARIES ARE THE UNIT OF CONVERSATIONAL IDENTITY: a machine that holds
|
|
586
|
+
an identity (a planner or a coder persona) declares its own `model:` once, at
|
|
587
|
+
the machine level, instead of repeating it per state — and, per the memory rule
|
|
588
|
+
above, two references to the SAME machine (a dedup instantiation) are always two
|
|
589
|
+
independent instances with two independent memory scopes, never one shared
|
|
590
|
+
conversation across both call sites.
|
|
591
|
+
|
|
487
592
|
Besides `it.vars` (below), a `script`/`prompt`/`message`/`commit` template sees:
|
|
488
593
|
|
|
489
|
-
- **`it.startCommit`** — the process's diff base (the commit the current
|
|
490
|
-
started from, or a
|
|
594
|
+
- **`it.startCommit`** — the process's diff base (the commit the current process
|
|
595
|
+
started from, or the base a `--var reviewBase=<commitish>` entry resolved to).
|
|
491
596
|
- **`it.reviewBase`** — the previous review round's boundary, falling back to
|
|
492
597
|
`it.startCommit` on a first review.
|
|
493
598
|
- **`it.retainedBase`** — the process's trace/retry boundary, what a squash
|
|
494
|
-
actually keeps (never moved by a
|
|
599
|
+
actually keeps (never moved by a review entry's fixed base).
|
|
495
600
|
- **`it.currentCommit`** / **`it.previousCommit`** — HEAD's hash and its parent,
|
|
496
601
|
at render time.
|
|
497
602
|
|
|
@@ -504,26 +609,103 @@ Authoring or editing a workflow with a coding agent? `skills/authoring/SKILL.md`
|
|
|
504
609
|
is the agent-facing contract for producing a valid `workflow:` — the state
|
|
505
610
|
model, pattern grammar, load-time rules, and how to verify a change compiles.
|
|
506
611
|
|
|
612
|
+
> **Upgrading from a pre-8.2 `workflow:`?** The old flat `states:` shape (with a
|
|
613
|
+
> per-state `initial: true`/`reviewEntry: true`/`fixEntry: true` flag) is no
|
|
614
|
+
> longer accepted — finish or `gtd abandon` any in-flight process before
|
|
615
|
+
> upgrading, since the old and new shapes aren't compatible mid-process. Wrap
|
|
616
|
+
> your states under a single
|
|
617
|
+
> `machines: { <name>: { entry: <initial state>, states: {...} } }` and declare
|
|
618
|
+
> `entry: { default: <name> }` at the top level (moving any
|
|
619
|
+
> `reviewEntry`/`fixEntry` state to a plain per-state `entry: true` flag,
|
|
620
|
+
> entered via `gtd --entry <state>` — see the next note).
|
|
621
|
+
|
|
622
|
+
> **Upgrading a `workflow:` that still declares `entry.review`/`entry.fix`?**
|
|
623
|
+
> Those two keys, and the `gtd review <commitish>`/`gtd fix` commands that used
|
|
624
|
+
> them, are gone. Replace `entry.review: <target>`/`entry.fix: <target>` with a
|
|
625
|
+
> plain `entry: true` flag on that same state, and enter it with
|
|
626
|
+
> `gtd step <actor> --entry <state>` (or the actor-less short form
|
|
627
|
+
> `gtd --entry <state>`) instead of the removed commands.
|
|
628
|
+
> `gtd review <commitish>` required a clean tree and a `<commitish>` argument;
|
|
629
|
+
> the replacement instead captures whatever is pending in the working tree (just
|
|
630
|
+
> like an ordinary `gtd step`) and takes the commitish as a
|
|
631
|
+
> `--var reviewBase=<commitish>` override consumed by that state's own
|
|
632
|
+
> template-form `reviewBase:` (see the `workflow:` shape above and
|
|
633
|
+
> [`gtd --entry`](#commands)). `gtd fix` likewise becomes
|
|
634
|
+
> `gtd --entry <the state that was entry.fix>` (e.g. the bundled template's
|
|
635
|
+
> `gtd --entry fix-precheck`).
|
|
636
|
+
|
|
637
|
+
> **Upgrading a `workflow:` that still declares a per-state `model:` or
|
|
638
|
+
> `memory:`?** `model:` moved from a state key to a MACHINE key: declare it once
|
|
639
|
+
> on the `machines.<name>:` entry instead of on every one of that machine's
|
|
640
|
+
> states — it is stamped onto every one of that machine's own `prompt` states
|
|
641
|
+
> automatically (see the `workflow:` shape above). A state that still declares
|
|
642
|
+
> its own `model:` is a load error naming the machine to move it to, never a
|
|
643
|
+
> silently ignored key. `memory:` is gone outright, with **no** replacement key
|
|
644
|
+
> — a workflow author simply removes it; a state's memory scope is now computed
|
|
645
|
+
> from its position in the machine tree instead of authored (see
|
|
646
|
+
> [Driving the loop](#driving-the-loop)). The bundled template was also
|
|
647
|
+
> restructured so machine boundaries line up with this new identity model,
|
|
648
|
+
> renaming twenty-two states:
|
|
649
|
+
>
|
|
650
|
+
> - `building` → `build.building`
|
|
651
|
+
> - `decompose` → `build.decompose` → `design.decompose`
|
|
652
|
+
> - `squashing` → `build.squashing`
|
|
653
|
+
> - `review.building` → `build.addressing`
|
|
654
|
+
> - `packages.building` → `packages.item.building`
|
|
655
|
+
> - `packages.closing` → `packages.item.closing`
|
|
656
|
+
> - `packages.health.check` → `packages.item.health.check`
|
|
657
|
+
> - `packages.health.fix` → `packages.item.fix-suite`
|
|
658
|
+
> - `packages.health.escalate` → `packages.item.health.escalate`
|
|
659
|
+
> - `packages.spec.review` → `packages.item.spec.review`
|
|
660
|
+
> - `packages.spec.fix` → `packages.item.fix-spec`
|
|
661
|
+
> - `build.check` → `build.health.check`
|
|
662
|
+
> - `build.escalate` → `build.health.escalate`
|
|
663
|
+
> - `review.reviewing` → `build.review.reviewing`
|
|
664
|
+
> - `review.await-review` → `build.review.await-review`
|
|
665
|
+
> - `review.deciding` → `build.review.deciding`
|
|
666
|
+
> - `review.collecting` → `build.review.collecting`
|
|
667
|
+
> - `product.author` → `design.product-author`
|
|
668
|
+
> - `product.answer` → `design.product-answer`
|
|
669
|
+
> - `technical.author` → `design.technical-author`
|
|
670
|
+
> - `technical.answer` → `design.technical-answer`
|
|
671
|
+
>
|
|
672
|
+
> (`decompose`'s two hops both land in this same release, so a process upgrading
|
|
673
|
+
> from before either restructure only ever sees one hop: `decompose` →
|
|
674
|
+
> `design.decompose`.) Because of these renames, an in-flight process left
|
|
675
|
+
> resting at one of the old qualified state names can no longer be resumed after
|
|
676
|
+
> upgrading — those names no longer exist in the definition, and gtd refuses
|
|
677
|
+
> loudly rather than silently treating the rest as idle. Run `gtd abandon` to
|
|
678
|
+
> discard it and start over (or finish the process on the pre-upgrade workflow
|
|
679
|
+
> version first).
|
|
680
|
+
|
|
507
681
|
### Variables
|
|
508
682
|
|
|
509
|
-
Every template — `script`/`prompt`/`message`/`commit`,
|
|
510
|
-
`
|
|
511
|
-
assembled from
|
|
683
|
+
Every template — `script`/`prompt`/`message`/`commit`, a machine's own `model:`,
|
|
684
|
+
and a state's `file:` — sees `it.vars`: a flat `Record<string, string>`
|
|
685
|
+
assembled from four layers, **later wins**:
|
|
512
686
|
|
|
513
|
-
1. **The workflow's own `vars:` key** (sibling to `
|
|
514
|
-
author's declared defaults. The unified template declares
|
|
515
|
-
`vars: { testCommand: "npm test" }`, read by `
|
|
687
|
+
1. **The workflow's own `vars:` key** (sibling to `entry:`/`machines:`) — the
|
|
688
|
+
workflow author's declared defaults. The unified template declares
|
|
689
|
+
`vars: { testCommand: "npm test" }`, read by `build.health.check`'s script as
|
|
516
690
|
`<%~ it.vars.testCommand %>`.
|
|
517
691
|
2. **A top-level `.gtdrc` `vars:` key** (a sibling of `workflow:`, NOT nested
|
|
518
692
|
inside it) — per-repo tuning without redefining the whole workflow.
|
|
519
|
-
3.
|
|
693
|
+
3. **The current process's entry `--var` overrides**, if it was started via
|
|
694
|
+
`gtd step <actor> --entry <state>`/`gtd --entry <state>` — repeatable
|
|
695
|
+
`--var <name>=<value>` flags fixed at the moment of entry and recorded as
|
|
696
|
+
`Gtd-Var: <name>=<value>` trailers on the process's oldest commit, re-parsed
|
|
697
|
+
on every turn for as long as that process is underway. Each `--var` name must
|
|
698
|
+
already be declared by layer 1 or 2; an undeclared name is a usage error, not
|
|
699
|
+
a silent no-op.
|
|
700
|
+
4. **`GTD_<UPPERCASE-name>` environment variables** — highest precedence,
|
|
520
701
|
checked at every invocation, case-insensitively against each name already
|
|
521
|
-
declared by
|
|
522
|
-
environment can only OVERRIDE a name
|
|
702
|
+
declared by layers 1–3: `GTD_TESTCOMMAND` overrides `testCommand`. The
|
|
703
|
+
environment can only OVERRIDE a name an earlier layer already declared — a
|
|
523
704
|
`GTD_*` var matching no declared name is silently ignored.
|
|
524
705
|
|
|
525
706
|
Values in layers 1–2 must be YAML scalars (string/number/boolean), coerced to
|
|
526
|
-
strings at load time; an object or array value is a load error.
|
|
707
|
+
strings at load time; an object or array value is a load error. A `--var` value
|
|
708
|
+
(layer 3) is always a single-line string as given on the command line.
|
|
527
709
|
|
|
528
710
|
```yaml
|
|
529
711
|
# .gtdrc — overriding the unified template's testCommand
|
|
@@ -566,9 +748,9 @@ Those findings include the **semantic graph checks**: every `on` target and
|
|
|
566
748
|
|
|
567
749
|
Many of these problems never reach gtd at all if your editor validates against
|
|
568
750
|
the [published schema](#schema), which fully types the `workflow:` key. The
|
|
569
|
-
rules JSON Schema cannot express — exactly one content kind,
|
|
570
|
-
|
|
571
|
-
compiler's job at load time.
|
|
751
|
+
rules JSON Schema cannot express — exactly one content kind, `entry.default`
|
|
752
|
+
resolving to a real state, targets naming defined states, reachability — remain
|
|
753
|
+
the compiler's job at load time.
|
|
572
754
|
|
|
573
755
|
## Repository requirements
|
|
574
756
|
|
|
@@ -607,13 +789,18 @@ own `- [ ]` line — diagnostics for both (live as you edit), and a
|
|
|
607
789
|
|
|
608
790
|
It is config-driven via each state's `file:`/`mode:`, and falls back to basename
|
|
609
791
|
dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and `review` are
|
|
610
|
-
gtd's built-in steering-file
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
792
|
+
gtd's built-in steering-file FORMATS, each with its own outline/actions/
|
|
793
|
+
go-to-definition and a VALIDATOR gtd itself implements; a mode's `format:` and
|
|
794
|
+
`validate:` are shell commands a workflow (or a project's `.gtdrc`) declares for
|
|
795
|
+
itself, so you bring your own formatter and your own checkers. Overriding one of
|
|
796
|
+
`qa`/`review`'s `validate:` with a shell command does NOT lose its outline/
|
|
797
|
+
actions — those come from the FORMAT (the name), independent of who validates —
|
|
798
|
+
but `gtd lsp` never runs a shell command per keystroke, so live diagnostics
|
|
799
|
+
become one `Information` notice pointing at `gtd validate` instead of the
|
|
800
|
+
built-in findings. Any OTHER mode name — `prose` (used by the simple flow's plan
|
|
801
|
+
file), or a project's own declared name — has no built-in format, so it gets no
|
|
802
|
+
live editor support at all: `gtd validate` and the `gtd step` gate still format
|
|
803
|
+
and validate it like any other mode.
|
|
617
804
|
|
|
618
805
|
## Development
|
|
619
806
|
|
|
@@ -622,8 +809,10 @@ npm install
|
|
|
622
809
|
npm run dev # run from source, no build (node dev/run.mjs)
|
|
623
810
|
npm run build # tsdown → dist/gtd.bundle.mjs
|
|
624
811
|
npm test # format:check, typecheck, lint, unit + e2e tests, fallow
|
|
625
|
-
npm run test:unit
|
|
626
|
-
npm run test:e2e
|
|
812
|
+
npm run test:unit # vitest unit tests (the pure resolver) — --project unit
|
|
813
|
+
npm run test:e2e # gherkin e2e, both tiers — test:e2e:inmem + test:e2e:live
|
|
814
|
+
npm run test:e2e:inmem # @inmem scenarios, in-process, no build — --project e2e-inmem
|
|
815
|
+
npm run test:e2e:live # @live scenarios against the built bundle — --project e2e-live
|
|
627
816
|
npm run test:mutation # StrykerJS mutation testing (manual only, ~10 min)
|
|
628
817
|
npm run typecheck
|
|
629
818
|
npm run lint
|
|
@@ -639,7 +828,20 @@ The decision core is pure and IO-free: the pattern machine's shape (states,
|
|
|
639
828
|
patterns, retry) is a plain-data `WorkflowDefinition` (`src/PatternMachine.ts`),
|
|
640
829
|
and the same module's `resolveState`/`step` are the pure resolver/interpreter
|
|
641
830
|
over it — so the whole engine is trivially unit-testable in isolation. All
|
|
642
|
-
git/filesystem/template IO is confined to the edge (`src/Edge.ts`)
|
|
831
|
+
git/filesystem/template IO is confined to the edge (`src/Edge.ts`), whose
|
|
832
|
+
`currentRest` resolves everything derivable from where the process rests right
|
|
833
|
+
now into a single snapshot, and whose `planStep`/`planEntry` pair separates
|
|
834
|
+
deciding a transition from performing it — so a command's own capture guards run
|
|
835
|
+
between the two. The edge reaches the outside world through two content ports
|
|
836
|
+
(`src/RepoFiles.ts` for working-tree and committed reads, `src/CommandRunner.ts`
|
|
837
|
+
for the one place gtd spawns a subprocess itself), with the `gtd step` capture
|
|
838
|
+
guards (`src/StepGuards.ts`) built on top of them.
|
|
839
|
+
|
|
840
|
+
`src/testing/` holds the in-memory git/config/filesystem test double every
|
|
841
|
+
`@inmem` scenario and unit test runs against instead of a real repo — it never
|
|
842
|
+
ships (a lint rule and a build-time check both enforce that) and is trustworthy
|
|
843
|
+
only because the same `GitOperations` contract suite runs against a real git
|
|
844
|
+
repo too.
|
|
643
845
|
|
|
644
846
|
Releases are automatic: push releasable Conventional Commits (`fix:`, `feat:`,
|
|
645
847
|
or breaking changes) to `main` and semantic-release computes the next version,
|