@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.
Files changed (5) hide show
  1. package/README.md +320 -118
  2. package/bin/gtd +80 -29
  3. package/dist/gtd.bundle.mjs +4684 -3247
  4. package/package.json +11 -9
  5. 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, test, review — from your repository state, and prints the next
10
- prompt for whatever agent you point at it. Every step is a commit. Tests are run
11
- by the tool and branched on by exit code, so the agent never grades its own
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 for). The two
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: two-phase
109
- product then technical Q&A (`.gtd/REQUIREMENTS.md` → `.gtd/ARCHITECTURE.md`) —
110
- each open question offers a couple of candidate answers plus a
111
- `- [ ] _your answer_` slot, and you tick exactly one per question (the gate
112
- won't let a phase advance while any question is unanswered) — then
113
- decomposition into work **packages** (each a set of independent tasks a single
114
- build turn fans out to parallel subagents), a per-package test loop, and a
115
- per-package **agentic review** that verifies the package against its spec.
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 cycle rests at that gate, gtd opens a
121
- **review checkout window**: HEAD is rewound to the review base with the working
122
- tree untouched, so the whole reviewable change shows up as ordinary uncommitted
123
- changes in your editor's normal git integration (and files added during the
124
- cycle show up as ordinary untracked files, so discarding one deletes it). The
125
- window closes on the next gtd invocation. Tick a box as you review each hunk
126
- (ticking just records "I read this"), and leave a **comment** to request
127
- changes: a note on a line, an inline `// TODO`-style comment in the code, or a
128
- direct code edit. Any comment sends a build + re-review round — an agent first
129
- turns your comments into an explicit instruction list, then a build turn
130
- implements it (a re-review then covers only the follow-through, and a hand-edit
131
- is treated as your own fix the agent completes without reverting your lines; a
132
- comment can't be silently dropped — a build turn that addresses nothing is
133
- refused). Ticking every box with no comment is the sign-off, which collapses the
134
- whole cycle into one commit (a **squash finale** whose message an agent drafts).
135
- Stepping with a box still unticked and no comment is refused (finish reviewing
136
- first), as is deleting `.gtd/REVIEW.md`.
137
-
138
- The same review tail also has a direct entry point — `gtd review <commitish>`
139
- starts a brand new process reviewing `<commitish>..HEAD` with no cycle of its
140
- own, e.g. a colleague's PR branch. Its squash keeps and describes only the fixes
141
- made _during_ the review (not the reviewed changeset); a clean sign-off with no
142
- fixes becomes an empty `chore: human review` commit. A fourth entry, `gtd fix`,
143
- starts from a clean `idle` and goes straight into repairing a red baseline —
144
- repair, review, and squash into one commit. If the suite is already green there
145
- is nothing to fix, and the log is left untouched — no commit is left behind.
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
- review <commitish>
188
- Start a NEW review process at the workflow's declared
189
- review-entry state (reviewEntry: true), reviewing
190
- <commitish>..HEAD — e.g. a colleague's PR branch. Requires
191
- a clean tree resting at the workflow's initial state
192
- fix Start a NEW process at the workflow's declared fix-entry
193
- state (fixEntry: true) that goes straight into repairing the
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
- --once (bare gtd or gtd loop only) run exactly one loop beat (one
228
- human-gate capture, one script check+step, or one agent
229
- prompt+step), then exit
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>`, and `--model=<name>` (the latter two only for
245
- `gtd step`) are the only long options the compiled bundle recognizes. Any other
246
- `--` option (including a typo like `--jsn`) is rejected with a usage error
247
- rather than silently ignored, so a mistyped flag can never degrade a JSON caller
248
- to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
249
- negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
250
- flag on any command other than `gtd step` are all usage errors.
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
- A state can also declare an optional `memory:` scope label: consecutive agent
317
- turns sharing the same label continue one agent session (remembered in the git
318
- dir, so it survives restarts across the same worktree), while a changed or
319
- absent label starts a fresh one. If the remembered session no longer exists
320
- (retention expired, `~/.claude/projects` wiped, a machine change), the driver
321
- degrades to a fresh session with a warning instead of stopping the loop.
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 cycle
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> # at least one of format/validate
531
+ format: <shell command> # both optional — {} is the format-only tier
462
532
  validate: <shell command>
463
- states:
533
+ entry:
534
+ default: <machine name> # which machine is the ROOT instance
535
+ machines:
464
536
  <name>:
465
- actor: <string> # forbidden on a commit state, required otherwise
466
- script: <string> # exactly one of script/prompt/message/commit
467
- prompt: <string>
468
- message: <string>
469
- commit: <string>
470
- on: # a mapping, DECLARATION ORDER PRESERVED
471
- "<pattern>": <targetState> # short form
472
- "<pattern>": {
473
- to: <targetState>,
474
- describe: <sentence>,
475
- action: <label>,
476
- } # description/action
477
- initial: true # exactly one state across the whole workflow
478
- retry:
479
- max: <number>
480
- otherwise: <targetState>
481
- model: <string> # optional, opaque harness hint — forbidden on a commit state
482
- memory: <string> # optional, opaque memory-scope label — forbidden on a commit state
483
- file: <string> # optional, an Eta template naming the state's steering file
484
- mode: <modeName> # optional, requires "file" — a built-in (qa/review/prose) or a `modes:` entry
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 cycle
490
- started from, or a `gtd review <commitish>` entry's resolved base).
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 `gtd review` entry).
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`, and
510
- `model`/`memory`/`file` — sees `it.vars`: a flat `Record<string, string>`
511
- assembled from three layers, **later wins**:
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 `states:`) — the workflow
514
- author's declared defaults. The unified template declares
515
- `vars: { testCommand: "npm test" }`, read by `checking`'s script as
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. **`GTD_<UPPERCASE-name>` environment variables** — highest precedence,
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 layer 1 or 2: `GTD_TESTCOMMAND` overrides `testCommand`. The
522
- environment can only OVERRIDE a name a config layer already declared — a
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, exactly one
570
- `initial: true`, targets naming defined states, reachability — remain the
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 MODES with a VALIDATOR gtd itself implements; a
611
- mode's `format:` and `validate:` are shell commands a workflow (or a project's
612
- `.gtdrc`) declares for itself, so you bring your own formatter and your own
613
- checkers. A third built-in, `prose` — format-only, no validator, used by the
614
- simple flow's plan file — has no live editor support: `gtd lsp` publishes no
615
- symbols or diagnostics for it, though `gtd validate` and the `gtd step` gate
616
- still format and validate it like any other mode.
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 # vitest unit tests (the pure resolver) — --project unit
626
- npm run test:e2e # gherkin e2e via vitest + quickpickle — --project 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,