@pmelab/gtd 9.1.2 → 9.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 +156 -107
- package/dist/gtd.bundle.mjs +147 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,10 @@ No chat scrollback. No lost sessions. No infinite fix loops. Just git.
|
|
|
21
21
|
- **Shareable.** Push the branch, and the workflow travels with it — the state
|
|
22
22
|
lives in the commits, so another machine (or another person) picks up exactly
|
|
23
23
|
where you left off.
|
|
24
|
-
- **Files, not chat.** Plans live in `.gtd
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- **Files, not chat.** Plans live in `.gtd/` steering files (`REQUIREMENTS.md`,
|
|
25
|
+
`ARCHITECTURE.md`, the package files under `.gtd/packages/`). Request changes
|
|
26
|
+
by editing them, approve by leaving the tree clean — all in your own editor.
|
|
27
|
+
There is no chat UI to lose.
|
|
27
28
|
- **Harness agnostic.** gtd emits prompts to stdout (or JSON). Claude Code, a
|
|
28
29
|
bash loop, a CI job, or you reading it out loud — the workflow doesn't care
|
|
29
30
|
who executes it.
|
|
@@ -61,17 +62,23 @@ gtd init
|
|
|
61
62
|
This writes a minimal `.gtdrc.json` seeding the one variable most projects
|
|
62
63
|
change — the test command (`vars.testCommand`, defaulting to `npm test`) — plus
|
|
63
64
|
a top-level `modes:` block suggesting **Prettier** as the steering-file
|
|
64
|
-
formatter (`npx prettier --write` for the built-in `qa`/`review
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
up. To customize the machine itself, add a `workflow:` key (there is no default
|
|
65
|
+
formatter (`npx prettier --write` for the built-in `qa`/`review` modes — format
|
|
66
|
+
only, so gtd still validates them); edit or drop either freely (point
|
|
67
|
+
`testCommand` at your suite, swap Prettier for dprint or a script, delete a
|
|
68
|
+
key). It writes **no** `workflow:` key — the machine is built in — so review and
|
|
69
|
+
commit the file before your first `gtd land`. `gtd init` takes no argument and
|
|
70
|
+
refuses to clobber an existing config; it may also run in a plain parent
|
|
71
|
+
directory (not a git repo) to seed a shared config a nested repo picks up. To
|
|
72
|
+
customize the machine itself, add a `workflow:` key (there is no default
|
|
73
73
|
fallback to merge over — a `workflow:` is the whole definition).
|
|
74
74
|
|
|
75
|
+
gtd requires a repository with **at least one commit** before any state command
|
|
76
|
+
(`land`, `--entry`, `next`, `status`, `abandon`, `restore`, `validate`) will run
|
|
77
|
+
— there is no workflow state to derive from an empty history. Committing the
|
|
78
|
+
`.gtdrc.json` above (or anything else) satisfies this by construction;
|
|
79
|
+
`gtd init`, `gtd install`, `gtd lsp`, `gtd visualize`, and `gtd check` are
|
|
80
|
+
unaffected since none of them derive workflow state.
|
|
81
|
+
|
|
75
82
|
## How it works
|
|
76
83
|
|
|
77
84
|
gtd is a small **pattern machine**: named states, each awaiting one actor and
|
|
@@ -110,76 +117,96 @@ together: the `action` leads when the edge declares one, with the raw pattern
|
|
|
110
117
|
(and `describe`) still shown alongside it, so a human choosing between routes
|
|
111
118
|
reads intent first, glob second.
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
120
|
+
**Your input is a spec, not the work.** The unified workflow treats ANY change
|
|
121
|
+
to the tree — a hand-edit to real code, a scratch note in some file, anything at
|
|
122
|
+
all — as a sketch to be finished, never as work to be preserved: there is no
|
|
123
|
+
fork on which steering file you happen to create, and `idle` has exactly one
|
|
124
|
+
outgoing edge, into `unwind`. `unwind` reverts that whole diff back out of your
|
|
125
|
+
working tree (`git revert --no-commit` against the commit it just landed in)
|
|
126
|
+
before planning ever starts — your tree is back to exactly what it was, and your
|
|
127
|
+
intent doesn't disappear, it survives in history for `design.triage` to read.
|
|
128
|
+
|
|
129
|
+
From there, `start-gate.check` runs your test suite on the tree as it now stands
|
|
130
|
+
— which, thanks to the unwind, is simply your repo's own baseline. The rule is
|
|
131
|
+
the plain one: the baseline must be green. A red run halts the process and tells
|
|
132
|
+
you to repair it first (that's what `gtd --entry fix-precheck` is for);
|
|
133
|
+
`review-gate` follows the same rule.
|
|
134
|
+
|
|
135
|
+
Once past the gate, `design.triage` reads the diff that started the process
|
|
136
|
+
itself — the prompt never inlines it; it finds the entry commit (the one
|
|
137
|
+
`unwind` reverted) and runs `git show` against it — and groups that diff into an
|
|
138
|
+
ORDERED list of **concerns**, each one able to leave the test suite green on its
|
|
139
|
+
own, classifying each as **product** (a user-facing/requirements decision) or
|
|
140
|
+
**technical** (an implementation decision). It raises open questions for the
|
|
141
|
+
product concerns only, in `.gtd/REQUIREMENTS.md`, folding EVERYTHING the entry
|
|
142
|
+
commit added into those concerns — a scratch sketch and a hand-edited code
|
|
143
|
+
change alike, since the unwind already reverted both; nothing is left to delete.
|
|
144
|
+
A shared check+answer gate then probes `.gtd/REQUIREMENTS.md` for unanswered
|
|
145
|
+
questions: each open question offers a couple of candidate answers plus a
|
|
146
|
+
`- [ ] _your answer_` slot, and you tick exactly one per question (the gate
|
|
147
|
+
won't let the phase advance while any question is unanswered) — but a phase
|
|
148
|
+
whose document has no open questions skips that human stop entirely and falls
|
|
149
|
+
straight through.
|
|
150
|
+
|
|
151
|
+
Once the product questions are settled, `architecture.author` picks up as a
|
|
152
|
+
**cold reader** — a separate machine with its own memory scope, so it does not
|
|
153
|
+
resume the triage conversation but reads `.gtd/REQUIREMENTS.md` in full — and
|
|
154
|
+
develops the _how_ for each settled concern, raising only technical open
|
|
155
|
+
questions in `.gtd/ARCHITECTURE.md` (routed through that same shared
|
|
156
|
+
check+answer gate shape, again skipping the human stop when nothing is open),
|
|
157
|
+
deleting `.gtd/REQUIREMENTS.md` once its content is folded in. Once those are
|
|
158
|
+
settled, `architecture.decompose` is a purely mechanical write-out — **one
|
|
159
|
+
package file per concern**, in the settled order, with no merge/split judgement
|
|
160
|
+
of its own (that already happened in triage's grouping) — handing off to the
|
|
161
|
+
per-package build queue: each package (a set of independent tasks a single build
|
|
162
|
+
turn fans out to parallel subagents) runs its own test loop and a per-package
|
|
163
|
+
**agentic review** that verifies it against its own spec. A package whose work
|
|
164
|
+
already landed (an earlier package's fix turn pulled it in) is not a dead end:
|
|
165
|
+
the build turn records per-criterion evidence in `.gtd/SATISFIED.md` instead of
|
|
166
|
+
implementing anything, and the package still goes through the checks and the
|
|
167
|
+
spec review before closing out. If a queue item ever _does_ dead-end (a build
|
|
168
|
+
turn that authors nothing stalls), the supported recovery is to write that same
|
|
169
|
+
file yourself and `gtd land` — no hand-authored state commit.
|
|
170
|
+
|
|
171
|
+
The process converges on that same shared tail: an agent hands you a
|
|
172
|
+
`.gtd/REVIEW.md` checkbox review of the diff — the prompt never inlines the diff
|
|
173
|
+
itself; it names the commit the changes are based at and the agent runs
|
|
174
|
+
`git diff` to read the range before writing the review. While the process rests
|
|
175
|
+
at that gate, the landing script opens a **review checkout window**: HEAD is
|
|
176
|
+
rewound to the review base with the working tree untouched, so the whole
|
|
177
|
+
reviewable change shows up as ordinary uncommitted changes in your editor's
|
|
178
|
+
normal git integration (and files added during the process show up as ordinary
|
|
179
|
+
untracked files, so discarding one deletes it — an untracked file you leave
|
|
180
|
+
alone is not a pending change, and only actually removing it from disk counts as
|
|
181
|
+
a deletion). The next landing's own script closes the window before it commits.
|
|
182
|
+
Tick a box as you review each hunk (ticking just records "I read this"), and
|
|
183
|
+
leave a **comment** to request changes: a note on a line, an inline
|
|
184
|
+
`// TODO`-style comment in the code, or a direct code edit. Any comment sends a
|
|
185
|
+
build + re-review round — an agent first turns your comments into an explicit
|
|
186
|
+
instruction list, then a build turn implements it (a re-review then covers only
|
|
187
|
+
the follow-through, and a hand-edit is treated as your own fix the agent
|
|
188
|
+
completes without reverting your lines; a comment can't be silently dropped — a
|
|
189
|
+
build turn that addresses nothing is refused). The build turn that follows
|
|
190
|
+
through on feedback and the turn that drafts the final squash message both
|
|
191
|
+
resume the same session that built the feature in the first place, since the
|
|
192
|
+
review tail is nested inside that build identity rather than sitting beside it.
|
|
193
|
+
Ticking every box with no comment is the sign-off, which collapses the whole
|
|
194
|
+
process into one commit (a **squash finale** whose message an agent drafts).
|
|
195
|
+
Landing with a box still unticked and no comment is refused (finish reviewing
|
|
196
|
+
first), as is deleting `.gtd/REVIEW.md`. Both refusals hold wherever the review
|
|
197
|
+
doc lives: repointing `reviewFile` out of `.gtd/` (say to `REVIEW.md` at the
|
|
198
|
+
repo root) changes nothing about them — your own edit to the review doc is never
|
|
199
|
+
mistaken for a code comment, and the doc's pre-turn copy is read at the review
|
|
200
|
+
window's saved head rather than at the rewound `HEAD`.
|
|
201
|
+
|
|
202
|
+
A second entry, the same review tail's own direct entry point —
|
|
176
203
|
`gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
|
|
177
204
|
process reviewing `<commitish>..HEAD` with no build of its own, e.g. a
|
|
178
205
|
colleague's PR branch (`review-gate.check`'s `reviewBase:` is a template bound
|
|
179
206
|
to the `reviewBase` var, so supplying it via `--var` fixes the whole process's
|
|
180
207
|
diff base to that commitish). Its squash keeps and describes only the fixes made
|
|
181
208
|
_during_ the review (not the reviewed changeset); a clean sign-off with no fixes
|
|
182
|
-
becomes an empty `chore: human review` commit. A
|
|
209
|
+
becomes an empty `chore: human review` commit. A third entry,
|
|
183
210
|
`gtd --entry fix-precheck`, starts from a clean `idle` and goes straight into
|
|
184
211
|
repairing a red baseline — repair, review, and squash into one commit. If the
|
|
185
212
|
suite is already green there is nothing to fix, and the log is left untouched —
|
|
@@ -191,10 +218,11 @@ no commit is left behind.
|
|
|
191
218
|
_extra_ reachability root (and drives a badge in `gtd visualize`) for a state
|
|
192
219
|
that would otherwise be unreachable from the ordinary `idle` rest —
|
|
193
220
|
`review-gate.check` and `fix-precheck` need it for exactly that reason, while
|
|
194
|
-
`
|
|
195
|
-
|
|
196
|
-
state flags
|
|
197
|
-
`entry: true` is not a precondition for `--entry`
|
|
221
|
+
`start-gate.check` carries it too (the bundled template dedups its two
|
|
222
|
+
`entryGate` instances — `start-gate` and `review-gate` — into one shared
|
|
223
|
+
machine, so flagging the shared state flags both) even though `idle` already
|
|
224
|
+
reaches it the ordinary way. `entry: true` is not a precondition for `--entry`
|
|
225
|
+
to target a state.
|
|
198
226
|
|
|
199
227
|
Every agent state routes its model through two `vars` tiers — `plannerModel`
|
|
200
228
|
(heavier planning and review) and `coderModel` (the coding turns) — so you can
|
|
@@ -280,7 +308,9 @@ Commands:
|
|
|
280
308
|
non-zero when there are any. Resolves no workflow state and
|
|
281
309
|
reads no config — standalone, runnable from any directory
|
|
282
310
|
with <mode>/<file> given explicitly. This is what a
|
|
283
|
-
workflow's emitted validation script invokes as a leaf step
|
|
311
|
+
workflow's emitted validation script invokes as a leaf step.
|
|
312
|
+
--open-questions runs the qa unanswered-questions predicate
|
|
313
|
+
instead (see --help)
|
|
284
314
|
install Print a complete, self-contained briefing that teaches an
|
|
285
315
|
agent (or a human) to build a gtd driver in any shell or
|
|
286
316
|
runtime — the self-serve version of README's 'Writing
|
|
@@ -303,6 +333,10 @@ Options:
|
|
|
303
333
|
(with --entry; repeatable) supply a fixed it.vars
|
|
304
334
|
override for the new process; the name must already be
|
|
305
335
|
declared by the workflow's own vars: or the .gtdrc vars:
|
|
336
|
+
--open-questions (gtd check only) ignore <mode>'s structural findings and
|
|
337
|
+
instead run the qa open-questions predicate over <file>,
|
|
338
|
+
printing each unanswered question one per line and exiting
|
|
339
|
+
non-zero when any remain
|
|
306
340
|
--version, -v Print version and exit
|
|
307
341
|
--help, -h Print this help and exit
|
|
308
342
|
```
|
|
@@ -383,17 +417,17 @@ exits. You act by editing files (answer a plan question, tick a review box, fix
|
|
|
383
417
|
code) and re-running it — your pending edit arrives as the loop's first beat
|
|
384
418
|
(`kind: "capture"`, landed immediately), so you never run `gtd land` by hand.
|
|
385
419
|
|
|
386
|
-
Some gates accept by INACTION instead:
|
|
387
|
-
|
|
388
|
-
"accept
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
no-op (see `gtd land`'s exit
|
|
395
|
-
|
|
396
|
-
actually next.
|
|
420
|
+
Some gates could accept by INACTION instead: a workflow may declare a `"C"`
|
|
421
|
+
(clean-tree) pattern on a human-actor state that routes onward, so changing
|
|
422
|
+
nothing there means "accept as-is" rather than nothing happening. That reaches
|
|
423
|
+
the driver as `kind: "message"` either way, which no inspection can tell apart
|
|
424
|
+
from a gate you have not read yet — so the driver treats its OPENING beat as
|
|
425
|
+
yours regardless and lands it: you re-ran it while resting there, and that
|
|
426
|
+
invocation IS the decision. Every later beat halts as usual, because a gate the
|
|
427
|
+
driver produced mid-run is one you have not seen. Landing an opening beat at a
|
|
428
|
+
gate with no `"C"` pattern is harmless — a benign no-op (see `gtd land`'s exit
|
|
429
|
+
codes below) — and the gate then prints on the next beat. A mid-process restart
|
|
430
|
+
simply resumes driving from whatever beat is actually next.
|
|
397
431
|
|
|
398
432
|
Anything richer at that boundary — opening your editor, desktop notifications,
|
|
399
433
|
terminal-multiplexer status — is the job of an outer wrapper around the driver,
|
|
@@ -758,17 +792,14 @@ emitted script opens by asserting its own precondition
|
|
|
758
792
|
and the same shape for a review window's saved ref — see `src/Emit.ts`'s
|
|
759
793
|
`headAssertion`/`reviewWindowAssertion`), so a script generated against a
|
|
760
794
|
repository state that has since moved refuses loudly instead of corrupting
|
|
761
|
-
anything.
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
yet — safe to retry verbatim) or gtd's next invocation reads the new real state
|
|
770
|
-
and emits a fresh script for what remains. A driver never needs its own
|
|
771
|
-
retry/resume logic beyond "if the script failed, ask gtd again."
|
|
795
|
+
anything. **Emitted scripts are re-runnable**: this is the single most important
|
|
796
|
+
property for a driver's recovery logic. Re-running a script that already fully
|
|
797
|
+
applied is a no-op (its git writes are `--allow-empty` commits, idempotent ref
|
|
798
|
+
updates, and tolerant staged-restore calls), and re-running one that only
|
|
799
|
+
partially applied resumes correctly, because the precondition either still holds
|
|
800
|
+
(nothing landed yet — safe to retry verbatim) or gtd's next invocation reads the
|
|
801
|
+
new real state and emits a fresh script for what remains. A driver never needs
|
|
802
|
+
its own retry/resume logic beyond "if the script failed, ask gtd again."
|
|
772
803
|
|
|
773
804
|
### Prerequisites
|
|
774
805
|
|
|
@@ -1037,6 +1068,24 @@ model, pattern grammar, load-time rules, and how to verify a change compiles.
|
|
|
1037
1068
|
> discard it and start over (or finish the process on the pre-upgrade workflow
|
|
1038
1069
|
> version first).
|
|
1039
1070
|
|
|
1071
|
+
> **Upgrading a `workflow:` from the old two-flow (`.gtd/TODO.md` vs.
|
|
1072
|
+
> `.gtd/REQUIREMENTS.md`) shape?** The bundled template collapsed that fork into
|
|
1073
|
+
> one flow: `idle` now has a single outgoing edge — any change at all starts the
|
|
1074
|
+
> process, never a fork on which steering file you create — and
|
|
1075
|
+
> `plan-gate.check`/`spec-gate.check` merged into one shared `start-gate.check`.
|
|
1076
|
+
> The old plan-iteration machine's `plan.planning`/`plan.await-plan` states and
|
|
1077
|
+
> its monolithic `build.building` state are gone outright; every concern now
|
|
1078
|
+
> goes through the same per-package build queue instead. Renamed:
|
|
1079
|
+
> `design.product-author` → `design.triage`, `design.product-answer` →
|
|
1080
|
+
> `design.gate.answer`, `design.technical-author` → `architecture.author`,
|
|
1081
|
+
> `design.technical-answer` → `architecture.gate.answer`, `design.decompose` →
|
|
1082
|
+
> `architecture.decompose` (architecture is now its own sibling machine with its
|
|
1083
|
+
> own memory scope, not nested under `design`). The `todoFile` var and the
|
|
1084
|
+
> `prose` mode are both gone — there is no more free-form plan file. As with
|
|
1085
|
+
> every rename above, an in-flight process resting at one of the old names can
|
|
1086
|
+
> no longer be resumed; `gtd abandon` it (or finish it on the pre-upgrade
|
|
1087
|
+
> workflow version) before upgrading.
|
|
1088
|
+
|
|
1040
1089
|
### Variables
|
|
1041
1090
|
|
|
1042
1091
|
Every template — `script`/`prompt`/`message`/`commit`, a machine's own `model:`,
|
|
@@ -1160,10 +1209,10 @@ itself, so you bring your own formatter and your own checkers. Overriding one of
|
|
|
1160
1209
|
actions — those come from the FORMAT (the name), independent of who validates —
|
|
1161
1210
|
but `gtd lsp` never runs a shell command per keystroke, so live diagnostics
|
|
1162
1211
|
become one `Information` notice pointing at `gtd validate` instead of the
|
|
1163
|
-
built-in findings. Any OTHER mode name —
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1212
|
+
built-in findings. Any OTHER mode name — a project's own declared name (e.g.
|
|
1213
|
+
`prose`, for a free-form note) — has no built-in format, so it gets no live
|
|
1214
|
+
editor support at all: `gtd validate`'s emitted script and the `gtd land` gate
|
|
1215
|
+
still format and validate it like any other mode.
|
|
1167
1216
|
|
|
1168
1217
|
## Development
|
|
1169
1218
|
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189865,9 +189865,9 @@ const STATE_FIELDS = {
|
|
|
189865
189865
|
* Optional. When `true`, a step at this state is REFUSED unless every OPEN
|
|
189866
189866
|
* question in its `qa`-mode `file:` is answered — EXACTLY ONE checkbox ticked
|
|
189867
189867
|
* per question (and, when the ticked one is the trailing free-text slot, its
|
|
189868
|
-
* text is non-empty). This is what makes the
|
|
189869
|
-
* (`
|
|
189870
|
-
* question before looping back or advancing. Like the review sign-off gate,
|
|
189868
|
+
* text is non-empty). This is what makes the bundled template's answer gates
|
|
189869
|
+
* (`design.gate.answer`/`architecture.gate.answer`) require a decision on
|
|
189870
|
+
* every question before looping back or advancing. Like the review sign-off gate,
|
|
189871
189871
|
* the PURE engine never reads it: the check lives at the edge
|
|
189872
189872
|
* (the answer-completeness guard in `src/StepGuards.ts`, over
|
|
189873
189873
|
* `src/OpenQuestions.ts`), and only acts when the state also declares
|
|
@@ -190369,8 +190369,8 @@ const validateActorShape = (name, state) => {
|
|
|
190369
190369
|
* present, may not be blank (a whitespace-only shell command would run and
|
|
190370
190370
|
* "succeed", silently disabling the gate). An empty entry (`{}`) is legal — the
|
|
190371
190371
|
* FORMAT-ONLY tier any workflow can use for a name with no gtd-side schema
|
|
190372
|
-
* (
|
|
190373
|
-
*
|
|
190372
|
+
* (e.g. a project's own `modes: { adr: {} }` before it plugs in any command
|
|
190373
|
+
* at all). The compiler
|
|
190374
190374
|
* (`src/PatternConfig.ts`) enforces the TYPES; these are the semantic rules,
|
|
190375
190375
|
* collected alongside every other finding.
|
|
190376
190376
|
*/
|
|
@@ -192198,7 +192198,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true, f
|
|
|
192198
192198
|
};
|
|
192199
192199
|
//#endregion
|
|
192200
192200
|
//#region src/workflows/unified.yaml
|
|
192201
|
-
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceModel`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`). Two\n# gate/queue machines below are deliberately IDENTITY-FREE (no `model:`):\n# `entryGate`/`healthGate` are infrastructure, not a persona, and `packageLoop`\n# is a bookkeeping queue, not a builder.\n#\n# * entryGate (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple (`plan-gate`), advanced\n# (`spec-gate`) and review (`review-gate`)\n# entries. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# simple build's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * advancedPlan (encap) — the advanced flow's one design conversation:\n# product Q&A, technical Q&A, and decomposition\n# (`design.*`: product-author/-answer,\n# technical-author/-answer, decompose) all share\n# ONE machine identity, so the agent explores the\n# codebase once, not three times, and the human's\n# answer rationale survives into decomposition.\n# ▸ planner.\n# * planLoop (encap) — the simple flow's plan-iteration loop (`plan.*`).\n# ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `simpleBuild` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — the implementer's OWN\n# follow-through on feedback is a coder action,\n# not a reviewer one, so it lives in\n# `build.addressing` instead.\n# * specReview (encap) — the advanced flow's per-package agentic review,\n# now a SINGLE state (`packages.item.spec.review`)\n# — a machine whose entire job is to be a\n# separate mind from the implementer. ▸ planner\n# — fixing the reviewer's own findings is a coder\n# action, so it lives in `packages.item.fix-spec`\n# instead.\n# * packageLoop (encap) — the advanced flow's per-package build queue\n# (`packages.picking`). Identity-free — the\n# actual per-package build identity lives in its\n# own `item` child (packageItem, below).\n# * packageItem (encap) — the advanced flow's per-package build identity\n# (`packages.item.*`: building, fix-suite,\n# fix-spec, closing), plus its own `health` and\n# `spec` children. ▸ coder.\n# * simpleBuild (encap) — the simple flow's build identity (`build.*`:\n# building, squashing, addressing, fix), plus its\n# own `health` child AND its own `review` child\n# (humanReview, above). ▸ coder.\n#\n# ── Why humanReview is nested, not a root sibling ───────────────────────────\n#\n# `humanReview` sits UNDER `simpleBuild` (`build.review`) rather than beside it\n# at the root, because a machine's memory scope is its dotted instance path\n# (see src/PatternMachine.ts's `memoryScopeAt`): entering a DESCENDANT scope\n# never breaks the parent's unbroken run, but entering a sibling does. The\n# review tail sits between the two coder turns that most need continuity —\n# `build.addressing` (follows through on review feedback) and `build.squashing`\n# (drafts the finale from the range this process built) — so hoisting it back\n# to a root sibling would force both of those turns to re-derive the feature\n# from scratch on every pass through the tail, instead of resuming the session\n# that built it. The reviewer itself is unaffected either way: `build.review.*`\n# is its own scope regardless of where it's nested, so it never resumes the\n# builder's session.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human blocked gate (fix the failures and re-run, exactly like the\n# escalate states), or is what `gtd --entry fix-precheck` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * the configured requirementsFile (default .gtd/REQUIREMENTS.md) ->\n# `spec-gate.check` -> the ADVANCED flow (one design conversation across\n# product + technical Q&A and decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. the configured todoFile, default .gtd/TODO.md) ->\n# `plan-gate.check` -> the SIMPLE flow (one grilling Q&A loop, a monolithic\n# build, no decomposition, no agentic review).\n#\n# A third entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A fourth, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All three converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which inspects\n# the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> build.squashing -> done (the ONLY path\n# to the finale — the whole process collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# build.review.deciding writes REVIEW_FEEDBACK.md -> build.review.collecting\n# implements it (completing any code the human hand-edited, never\n# reverting their lines) -> build.addressing (a coder action, not a\n# reviewer one — see \"Why humanReview is nested\" above) -> back through\n# `build.health.check` (the shared health pair, bound via the\n# `$onFeedback`/local `review` reference) -> build.review.reviewing\n# regenerates an INCREMENTAL REVIEW.md (build.review.deciding carries\n# `reviewBase: true`, so each feedback round's review + checkout window\n# covers only `last-review..HEAD`, i.e. the agent's follow-through, not the\n# human's own edit) -> await-review, looping until sign-off.\n# Two dead ends never commit: the review-signoff guard (src/StepGuards.ts)\n# REFUSES a step that leaves a box unticked with no comment (finish reviewing\n# first) and a deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health pair (`build.health.check`/`.escalate`, plus the caller's\n# own `build.fix`) serves the simple build and the feedback loop — its green\n# edge targets `build.review.reviewing`. The advanced per-package loop keeps\n# its OWN health pair (`packages.item.health.check`/`.escalate`, plus\n# `packages.item.fix-suite`) because its green edge targets the per-package\n# `packages.item.spec.review` gate instead.\n#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see the README's \"Variables\" section).\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both.\n stateDir: .gtd\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n # Written by packages.item.building when a package's acceptance criteria are\n # already met (an earlier package's fix turn pulled the work in); read by\n # the spec reviewer as the evidence a clean turn there is a real approval,\n # not a lazy/crashed one; removed by packages.item.closing.\n satisfiedFile: .gtd/SATISFIED.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot planning/technical-qa/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n\n# `prose` is a format-only steering-file mode this workflow blesses for itself:\n# a curated free-form document with no gtd-side schema (the simple flow's plan\n# file, .gtd/TODO.md — see the `plan.planning` state below). An empty entry is\n# the format-only tier any workflow can declare for a name with no built-in\n# parser; `qa`/`review` need no entry here — src/PatternConfig.ts's compiler\n# seeds those two from the built-in registry (src/SteeringFormats.ts)\n# automatically.\nmodes:\n prose: {}\n\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts (its script is the shared suite-check\n # wrapper — red -> the configured feedbackFile, green -> remove it); the `on`\n # rules turn that into \"halt at blocked\" vs \"proceed to $onGreen\". The script\n # and blocked message/describe differ per entry, so they ride in as `with:`\n # params. Kept SEPARATE from `healthGate` (below) even though both are a\n # check+something-else shape: this gate runs BEFORE planning, so it must NOT\n # sweep the plan/review steering files healthGate does — a human's fresh\n # sketch legitimately still exists here.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # Anchored here and aliased by `fix-precheck` (`gtd --entry\n # fix-precheck` entry): one suite-check script shared by all four\n # call sites (this machine's ×3 instances plus `fix-precheck`)\n # instead of four source copies. These gates run BEFORE planning, so\n # — unlike `healthGate.check` — they must NOT sweep\n # `todoFile`/`requirementsFile`: the human's fresh sketch still\n # legitimately exists here.\n # `entry: true` on the shared local marks all three `entryGate`\n # instances (plan-gate/spec-gate/review-gate) as manual entries —\n # only `review-gate.check` actually needs the reachability root (it\n # is otherwise unreachable from `entries.default`), but the shared\n # dedup makes marking one mark all three. Accepted as correct: all\n # three ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `plan-gate`/`spec-gate` bind the\n # literal empty string, which compiles away to \"no base\" here — they\n # are reached from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records the configured feedbackFile and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the simple build's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # This script ALSO sweeps spent steering files (`todoFile`,\n # `reviewRawFile`, `reviewFeedbackFile`) — a mechanic `entryGate.check`\n # must NOT run, since it executes before planning, while a fresh\n # sketch legitimately still exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep steering files spent by the states that led here: the plan\n # (building), the raw review capture (review.collecting), and the\n # feedback instructions (build.addressing) — each `rm -f` is a\n # no-op when the file is already gone or never existed on this path.\n todo=\"<%~ it.vars.todoFile %>\"\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n reviewFeedback=\"<%~ it.vars.reviewFeedbackFile %>\"\n rm -f \"$todo\" \"$reviewRaw\" \"$reviewFeedback\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": $onRed\n \"M <%= it.vars.feedbackFile %>\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── ENCAPSULATION: the advanced flow's design conversation ─────────────────\n # One planner identity across product Q&A -> technical Q&A -> decomposition:\n # a single codebase exploration instead of three, and the human's answer\n # rationale survives from product into technical and into decomposition (all\n # five states share one memory scope, `design`). Replaces the former\n # `qaLoop ×2` (which split one design conversation into two minds that each\n # re-explored the codebase) plus `simpleBuild`'s own `decompose` (which\n # landed on the coder tier only because it happened to live in the build\n # machine — decomposition is judgement-heavy planning work, not building).\n # ▸ planner identity — `model:` declared once here, stamped onto\n # `product-author`, `technical-author`, and `decompose` (its own prompt\n # states; the `*-answer` states are human turns and carry no prompt).\n advancedPlan:\n model: <%= it.vars.plannerModel %>\n params: [onPackages]\n entry: product-author\n states:\n product-author:\n actor: agent\n label: Refining the product plan\n file: <%= it.vars.requirementsFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this turn\n touches is `<%= it.vars.requirementsFile %>`; develop it. Do not\n create other files to hold notes or output.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for TWO\n concrete options plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n on:\n \"* **\": product-answer\n\n product-answer:\n actor: human\n label: Awaiting your answers\n file: <%= it.vars.requirementsFile %>\n mode: qa\n answerGate: true\n message: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: technical-author\n action: Accept answers\n describe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n technical Q&A (**design.technical-author**).\n \"* **\":\n to: product-author\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**design.product-author**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n technical-author:\n actor: agent\n label: Refining the technical plan\n file: <%= it.vars.architectureFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n If you developed those requirements earlier in this conversation, work\n from what you already settled; otherwise read\n `<%= it.vars.requirementsFile %>` (the converged product plan) yourself.\n Develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n on:\n \"* **\": technical-answer\n\n technical-answer:\n actor: human\n label: Awaiting your answers\n file: <%= it.vars.architectureFile %>\n mode: qa\n answerGate: true\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: decompose\n action: Accept answers\n describe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n decomposing the work into packages (**design.decompose**).\n \"* **\":\n to: technical-author\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**design.technical-author**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `<%= it.vars.architectureFile %>` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. Decompose it into an ordered\n set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...).\n\n Every package must leave the test suite GREEN on its own, in the order\n you write them. If a package's change would red-light tests that a\n later package is meant to fix, that is not a valid split: MERGE the\n two into one package. Never annotate a package with \"expected\n sequencing\" or \"this will fail until package N\" — an expected-red\n window is a decomposition error, not a note.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": $onPackages\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `planning`\n # (its only prompt state).\n planLoop:\n model: <%= it.vars.plannerModel %>\n params: [onAccept]\n entry: planning\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n mode: prose\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.todoFile %>`; develop it into the plan.\n Do not create other files to hold notes or output.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build — or, on a\n return lap, the plan you already wrote plus the human's edits and comments\n on it. Read it, explore the codebase, and develop it into a concrete\n implementation plan: the files to change, the approach, and the steps to\n take.\n\n Do NOT ask questions and do NOT add an \"Open Questions\" section — decide\n every open point yourself and write the plan as a set of concrete\n decisions. Where a decision is genuinely a judgement call, state the\n choice you made and, in one line, why. The human iterates on the plan by\n editing it directly, not by answering questions.\n\n On a return lap, `<%= it.vars.todoFile %>` carries the human's edits and\n inline comments on your previous plan. If you wrote that plan earlier in\n this conversation, work from what you already know plus these edits;\n otherwise read the file fresh. Fold every edit and comment into a\n revised, self-contained plan and remove the addressed comments — the plan\n is always the current, clean proposal, never a diff.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd land` yourself; the harness does\n that.\n on:\n \"* **\": await-plan\n\n await-plan:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\n mode: prose\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n action: Accept plan\n describe: >-\n change nothing to move on to implementation (**build.building**).\n \"* **\":\n to: planning\n action: Revise plan\n describe: >-\n rewrite a step, add a constraint, or leave an inline comment to\n send it back for another round (**plan.planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). The implementer's OWN\n # follow-through on feedback is a coder action, not a reviewer one — it\n # lives in `build.addressing` (`$onFeedback`), not inside this machine.\n humanReview:\n model: <%= it.vars.plannerModel %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the review-signoff guard (`src/StepGuards.ts`)\n # catches them BEFORE the step commits and refuses: an unfinished\n # review (a box still `- [ ]` with no comment) and a deleted REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to collapse the process into\n one commit (**build.squashing**).\n - **Request changes** — leave a comment: a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review.deciding** → **build.addressing**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (the sign-off gate\n # refuses an incomplete step, so `deciding` is entered exactly once per\n # round — nothing commits in between). It is FEEDBACK when the human left\n # a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\" tick) OR a\n # hand-edit to any file this round other than `.gtd/` or the state's own\n # reviewFile (which is var-configurable and may live outside `.gtd/`, e.g.\n # a root-level REVIEW.md — issue #128). Otherwise it is a clean sign-off.\n # This turn only CAPTURES the raw material into REVIEW_RAW.md (the\n # `collecting` agent then turns it into instructions); it does NOT\n # interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.vars.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n head=$(git rev-parse HEAD)\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it\n # is excluded by exact path, not just the stateDir prefix)?\n if git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v \"^$stateDir/\" | grep -vxF \"$reviewFile\" | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the changes and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions — it names a commit, never inlined content.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in $reviewFile at this commit; their hand edits,\"\n echo \"if any, are in that commit's other paths. Run: git show $head\"\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": collecting\n \"M <%= it.vars.reviewRawFile %>\": collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.reviewRawFile %>` (read it, then\n delete it) and `<%= it.vars.reviewFeedbackFile %>` (write it) — do\n NOT change any other file, you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A <%= it.vars.reviewFeedbackFile %>\": $onFeedback\n \"M <%= it.vars.reviewFeedbackFile %>\": $onFeedback\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n # A single state: a machine whose entire job is to be a separate MIND from\n # the implementer, distinct from the coder identity that builds/fixes the\n # package (`packages.item.*`). ▸ planner identity — `model:` declared once\n # here, stamped onto `review` (its only prompt state). Fixing the reviewer's\n # own findings is a coder action, not a reviewer one — it lives in\n # `packages.item.fix-spec` (`$onFix`), not inside this machine.\n specReview:\n model: <%= it.vars.plannerModel %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # Bounded per process: once entered `max` times, a transition into it is\n # redirected to `$onApproved` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `build.fix` cap has (see the README's \"Why\" section, \"Bounded, not\n # runaway\").\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `<%= it.vars.specFeedbackFile %>`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A <%= it.vars.specFeedbackFile %>\": $onFix\n \"M <%= it.vars.specFeedbackFile %>\": $onFix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the advanced flow's spent Q&A steering files: requirementsFile\n # is consumed by technical-qa, architectureFile by decompose — both are\n # gone by the time the package queue starts. No-op if already removed.\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n rm -f \"$requirements\" \"$architecture\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the advanced flow's per-package build identity ──────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `<%= it.vars.satisfiedFile %>` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `<%= it.vars.nextFile %>` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `<%= it.vars.satisfiedFile %>` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A <%= it.vars.satisfiedFile %>\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M <%= it.vars.satisfiedFile %>\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %> <%~ it.vars.satisfiedFile %>\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the simple flow's build identity ────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `squashing`, `addressing`, and `fix` (its own prompt states). Decomposition\n # (`design.decompose`) belongs to the planner tier now, not here — see\n # `advancedPlan` below. `addressing` is the implementer's OWN follow-through\n # on review feedback (a coder action) — reached via `$onFeedback`, bound at\n # its own `review:` reference below, not nested inside the (planner-only)\n # `humanReview` machine. `humanReview` is nested HERE (not at the root) so\n # the review tail's run stays inside the builder's own scope — see the\n # header comment.\n simpleBuild:\n model: <%= it.vars.coderModel %>\n params: [onDone]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state file this turn touches is\n `<%= it.vars.todoFile %>`; delete it once its work is complete. Do not\n create other files to hold notes or output.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n prompt: |\n You are an autonomous coding agent. The process is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `<%= it.vars.commitMsgFile %>` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `<%= it.vars.commitMsgFile %>` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A <%= it.vars.commitMsgFile %>\": $onDone\n \"M <%= it.vars.commitMsgFile %>\": $onDone\n\n addressing:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The\n # feedback-progress guard (`src/StepGuards.ts`) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. This\n workflow steers itself through its own state files — treat them as\n its private scratchpad, never as project code or documentation. The\n only state file this turn touches is\n `<%= it.vars.reviewFeedbackFile %>`; address it, then delete it. Do\n not create other files to hold notes or output.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": health.check\n\n fix:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so its run stays\n # inside the builder's own scope across the feedback round-trip — see\n # the header comment. Sign-off -> squashing; feedback -> addressing (the\n # implementer's own coder-identity follow-through, not a reviewer action).\n review:\n machine: humanReview\n with:\n onSignoff: squashing\n onFeedback: addressing\n\n # The root machine — the whole \"advanced/simple\" flow tree described in\n # this file's header comment. WIRING ONLY: no identity of its own, just the\n # shared entry/exit states and the top-level machine tree.\n unified:\n entry: idle\n states:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n label: Idle\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd process.\n\n To start one, create a steering file describing what you want built:\n\n - `<%= it.vars.todoFile %>` — a short sketch — starts the SIMPLE flow\n (plan, build, review).\n - `<%= it.vars.requirementsFile %>` — the product requirements — starts\n the ADVANCED flow (product + technical Q&A, package decomposition,\n parallel build, per-package agentic review).\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* <%= it.vars.requirementsFile %>\":\n to: spec-gate\n action: Start advanced flow\n describe: >-\n save the product requirements to `.gtd/REQUIREMENTS.md` — gtd first\n checks the test baseline is green, then the agent develops them\n through product then technical Q&A, decomposes the work into\n packages, and builds each in parallel (**spec-gate.check**).\n \"* **\":\n to: plan-gate\n action: Start simple flow\n describe: >-\n save a short sketch to `.gtd/TODO.md` (or any other change) — gtd\n first checks the test baseline is green, then the agent reads it,\n explores the codebase, and develops it into a concrete\n implementation plan (**plan-gate.check**).\n\n # ── Green-baseline gate: `gtd --entry fix-precheck` entry ─────────────────\n # `gtd --entry fix-precheck` enters HERE: run the suite, then a red run\n # drops straight into the shared `build.fix` loop (-> build.health.check ->\n # review + squash tail); a green run is a no-op back to `idle` (nothing\n # to fix).\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A <%= it.vars.feedbackFile %>\": build.fix\n \"M <%= it.vars.feedbackFile %>\": build.fix\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Green-baseline gate: simple flow entry. ────────────────────────────────\n plan-gate:\n machine: entryGate\n with:\n onGreen: plan\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**plan-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: advanced flow entry. ──────────────────────────────\n spec-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**spec-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Advanced flow: the design conversation (design.product-author/.product-answer ─\n # -> design.technical-author/.technical-answer -> design.decompose -> packages). ──\n design:\n machine: advancedPlan\n with:\n onPackages: packages\n\n # ── Simple flow plan-iteration loop (plan.planning/.await-plan -> build.building). ──\n plan:\n machine: planLoop\n with:\n onAccept: build.building\n\n # ── Advanced per-package build queue (packages.* -> build.review). ────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── Simple flow's build identity (build.* -> build.review -> done). ───────\n build:\n machine: simpleBuild\n with:\n onPackages: packages\n onDone: done\n";
|
|
192201
|
+
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceModel`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`).\n#\n# * entryGate (dedup ×2) — the green-baseline gate (check + human blocked)\n# guarding the single start (`start-gate`) and\n# the review (`review-gate`) entries. Its check\n# script IS the shared `&suiteCheck` anchor\n# (also aliased by `fix-precheck`, outside this\n# machine) — every entry blocks on any red tree,\n# full stop; there is no baseline-aware second\n# run any more, since `unwind` (below) already\n# put the tree back to the baseline before this\n# gate ever runs. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# build tail's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * questionGate (dedup ×2) — the check/answer pair shared by the design and\n# architecture phases' own open-questions gate\n# (`design.gate.*` / `architecture.gate.*`): a\n# shared probe script decides whether the\n# phase's steering file still has unanswered\n# questions, and only then rests the process at\n# a human answer gate. Identity-free.\n# * designPlan (encap) — the triage phase's one conversation: group the\n# diff that started the process into ordered,\n# classified concerns and resolve the PRODUCT\n# ones' open questions (`design.*`: triage,\n# gate.check, gate.answer). ▸ planner.\n# * archPlan (encap) — the technical phase's one conversation: a COLD\n# read of the settled concerns, resolving the\n# TECHNICAL open questions, then a mechanical\n# decomposition into package files\n# (`architecture.*`: author, gate.check,\n# gate.answer, decompose). A separate machine\n# from `designPlan` (not one fused machine) —\n# its own memory scope, since there may be a\n# handover between the user-facing and the\n# technical phase. ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `buildTail` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — the implementer's OWN\n# follow-through on feedback is a coder action,\n# not a reviewer one, so it lives in\n# `build.addressing` instead.\n# * specReview (encap) — the per-package agentic review, a SINGLE\n# state (`packages.item.spec.review`) — a\n# machine whose entire job is to be a separate\n# mind from the implementer. ▸ planner — fixing\n# the reviewer's own findings is a coder action,\n# so it lives in `packages.item.fix-spec`\n# instead.\n# * packageLoop (encap) — the per-package build queue\n# (`packages.picking`). Identity-free — the\n# actual per-package build identity lives in its\n# own `item` child (packageItem, below).\n# * packageItem (encap) — the per-package build identity (`packages.\n# item.*`: building, fix-suite, fix-spec,\n# closing), plus its own `health` and `spec`\n# children. ▸ coder.\n# * buildTail (encap) — the process's build identity (`build.*`:\n# squashing, addressing, fix), plus its own\n# `health` child AND its own `review` child\n# (humanReview, above). No monolithic \"build the\n# whole plan\" state any more — every package is\n# built inside `packages.item`, and the queue\n# drains straight into `build.review`. ▸ coder.\n#\n# ── Why humanReview is nested, not a root sibling ───────────────────────────\n#\n# `humanReview` sits UNDER `buildTail` (`build.review`) rather than beside it\n# at the root, because a machine's memory scope is its dotted instance path\n# (see src/PatternMachine.ts's `memoryScopeAt`): entering a DESCENDANT scope\n# never breaks the parent's unbroken run, but entering a sibling does. The\n# review tail sits between the two coder turns that most need continuity —\n# `build.addressing` (follows through on review feedback) and `build.squashing`\n# (drafts the finale from the range this process built) — so hoisting it back\n# to a root sibling would force both of those turns to re-derive the feature\n# from scratch on every pass through the tail, instead of resuming the session\n# that built it. The reviewer itself is unaffected either way: `build.review.*`\n# is its own scope regardless of where it's nested, so it never resumes the\n# builder's session.\n#\n# ── One flow: triage into concerns, then packages, then the shared tail ────\n#\n# ANY change to the tree starts the process — a hand-edit to real code, a\n# scratch note, anything at all — because it's a SKETCH, not finished work:\n# there is no fork on which steering file the human happened to create;\n# `idle` has exactly one outgoing edge, into `unwind`, which reverts that\n# whole diff back out of the working tree before planning starts (its intent\n# survives in history for `design.triage` to read; the squash finale means it\n# never reaches the final commit either way). `unwind` itself has exactly one\n# outgoing edge, into `start-gate`.\n#\n# EVERY entry gates on a green baseline before proceeding, and all three now\n# keep the exact same plain rule: block on any red tree, full stop. A red run\n# at any of them halts at a human blocked gate (fix the failures and re-run,\n# exactly like the escalate states). `start-gate` needs no baseline-aware\n# second run any more — by the time it runs, `unwind` has already put the\n# tree back to the baseline, so ONE suite run there IS the baseline verdict.\n#\n# Once green, `design.triage` reads the diff that started the process (never\n# rendered into the prompt — it names the start commit and reads the range up\n# to the unwind commit itself) and groups it into an ORDERED list of\n# CONCERNS, each one leaving the suite green on its own, classifying each as\n# PRODUCT or TECHNICAL. It raises open questions for the product ones only,\n# and folds EVERYTHING the start diff added into the concerns — a scratch\n# sketch and a real hand-edit alike, since `unwind` already emptied the tree\n# of both; nothing is left to delete. `design.gate` is a shared check+answer\n# pair: its check probes `requirementsFile` for unanswered questions and only\n# rests the process at a human answer gate when some remain — a\n# question-free phase skips the human stop entirely.\n#\n# Once the product questions are settled, `architecture.author` picks up as a\n# COLD reader (a separate machine, a separate memory scope — it reads\n# `requirementsFile` in full rather than resuming design's conversation),\n# develops the *how* for each settled concern, and raises only TECHNICAL open\n# questions — routed through the same shared `questionGate` shape\n# (`architecture.gate`) over `architectureFile`. Once those are settled,\n# `architecture.decompose` is a purely mechanical write-out: one package file\n# per concern, in the settled order — no merge/split judgement, since that\n# already happened in triage's grouping.\n#\n# From there the per-package build queue (`packages.*`) and the shared review\n# + squash tail (`build.*`) are UNCHANGED from before this restructure — see\n# their own machine doc comments below for the per-package build/review/\n# health mechanics.\n#\n# A second entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A third, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All entries converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which inspects\n# the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> build.squashing -> done (the ONLY path\n# to the finale — the whole process collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# build.review.deciding writes REVIEW_FEEDBACK.md -> build.review.collecting\n# implements it (completing any code the human hand-edited, never\n# reverting their lines) -> build.addressing (a coder action, not a\n# reviewer one — see \"Why humanReview is nested\" above) -> back through\n# `build.health.check` (the shared health pair, bound via the\n# `$onFeedback`/local `review` reference) -> build.review.reviewing\n# regenerates an INCREMENTAL REVIEW.md (build.review.deciding carries\n# `reviewBase: true`, so each feedback round's review + checkout window\n# covers only `last-review..HEAD`, i.e. the agent's follow-through, not the\n# human's own edit) -> await-review, looping until sign-off.\n# Two dead ends never commit: the review-signoff guard (src/StepGuards.ts)\n# REFUSES a step that leaves a box unticked with no comment (finish reviewing\n# first) and a deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health pair (`build.health.check`/`.escalate`, plus its own\n# `build.fix`) serves the feedback loop — its green edge targets\n# `build.review.reviewing`. The per-package loop keeps its OWN health pair\n# (`packages.item.health.check`/`.escalate`, plus `packages.item.fix-suite`)\n# because its green edge targets the per-package `packages.item.spec.review`\n# gate instead.\n#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see the README's \"Variables\" section).\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both.\n stateDir: .gtd\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n # Stamped (never authored, never read for its content) by design.gate.check\n # / architecture.gate.check — its mere presence is the routing signal that\n # the current phase's steering file still has unanswered questions.\n questionsFile: .gtd/QUESTIONS.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n # Written by packages.item.building when a package's acceptance criteria are\n # already met (an earlier package's fix turn pulled the work in); read by\n # the spec reviewer as the evidence a clean turn there is a real approval,\n # not a lazy/crashed one; removed by packages.item.closing.\n satisfiedFile: .gtd/SATISFIED.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot triage/design/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts — both `start-gate` and\n # `review-gate` reach this one check, and its script IS the shared\n # `&suiteCheck` anchor (defined once, right here, and aliased by\n # `fix-precheck` below); the `on` rules turn the result into \"halt at\n # blocked\" vs \"proceed to $onGreen\". Kept SEPARATE from `healthGate` (below)\n # even though both are a check+something-else shape: this gate runs BEFORE\n # planning, so it must NOT sweep any steering file `healthGate` does.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n # `entry: true` on the shared local marks both `entryGate` instances\n # (start-gate/review-gate) as manual entries — only `review-gate.check`\n # actually needs the reachability root (it is otherwise unreachable\n # from `entries.default`), but the shared dedup makes marking one mark\n # both. Accepted as correct: both ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `start-gate` binds the literal\n # empty string, which compiles away to \"no base\" here — it is reached\n # from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records the configured feedbackFile and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the build tail's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # This script ALSO sweeps spent steering files (`reviewRawFile`,\n # `reviewFeedbackFile`) — a mechanic `entryGate.check` must NOT run,\n # since it executes before planning, while a fresh sketch legitimately\n # still exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep steering files spent by the states that led here: the raw\n # review capture (review.collecting) and the feedback instructions\n # (build.addressing) — each `rm -f` is a no-op when the file is\n # already gone or never existed on this path.\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n reviewFeedback=\"<%~ it.vars.reviewFeedbackFile %>\"\n rm -f \"$reviewRaw\" \"$reviewFeedback\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": $onRed\n \"M <%= it.vars.feedbackFile %>\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── DEDUP: the open-questions check/answer pair (identity-free) ───────────\n # Shared by design and architecture's own gate (`design.gate`/\n # `architecture.gate`): `check`'s script probes whichever of\n # requirementsFile/architectureFile currently exists (see the script's own\n # comment) with `gtd check qa --open-questions` and stamps/removes\n # `questionsFile` on the result; only when it's present does the process\n # rest at `answer`, a human gate. A question-free phase never rests here at\n # all — `check`'s own `on` rows skip straight to `$onNone`. Takes NO `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path cannot be spliced into a shared script body —\n # `file` is instead the PARAM `answer` renders as its own `file:` (the\n # human-facing gate), a different use entirely from the probe.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env bash\n # gtd check turn — probe the current phase's steering file for\n # unanswered questions; an answered/empty result removes the marker\n # (questionsFile), an unanswered one stamps it fresh. Design and\n # architecture share this one probe: whichever of\n # requirementsFile/architectureFile exists ON DISK right now IS this\n # phase's steering file — unambiguous because architecture.author\n # deletes requirementsFile in the same turn it writes\n # architectureFile, so exactly one of the two exists here.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n questions=\"<%~ it.vars.questionsFile %>\"\n mkdir -p \"$(dirname \"$questions\")\"\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n file=\"$requirements\"\n [ -f \"$file\" ] || file=\"$architecture\"\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > \"$questions\"\n # A repeat identical result would otherwise leave the marker\n # byte-identical to its committed copy — no diff, so the check\n # would look answered. Stamp each round with the current HEAD\n # (which always advances between checks) so a still-open re-run\n # always re-registers as an M/A edit and takes the answer edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$questions\"\n else\n rm -f \"$questions\"\n fi\n on:\n \"A <%= it.vars.questionsFile %>\": answer\n \"M <%= it.vars.questionsFile %>\": answer\n \"D <%= it.vars.questionsFile %>\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # ── ENCAPSULATION: the triage phase's one conversation ─────────────────────\n # Groups the diff that started the process into ordered, classified\n # concerns and resolves the PRODUCT ones' open questions — one machine\n # identity across the whole back-and-forth, so the agent explores the\n # codebase once and the human's answer rationale survives every return lap.\n # ▸ planner identity — `model:` declared once here, stamped onto `triage`\n # (its only prompt state; `gate.answer` is a human turn and carries no\n # prompt).\n designPlan:\n model: <%= it.vars.plannerModel %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: <%= it.vars.requirementsFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.requirementsFile %>`; develop it. Do\n not create other files to hold notes or output.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n On the FIRST lap, `<%= it.vars.requirementsFile %>` does not exist\n yet (or holds whatever the human's own change happened to leave\n there). Build it from that start diff:\n\n 1. Group the diff into an ORDERED list of CONCERNS — each one a\n coherent piece of work that can leave the test suite GREEN on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n 2. Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n 3. For every PRODUCT concern with an open point you cannot settle\n yourself, add a question under a `## Open Questions` heading near\n the top: one `### <question>` sub-heading each, followed by a\n checkbox list of candidate answers — aim for TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Raise questions ONLY for product concerns here — a technical\n concern's open points wait for the next phase. Never tick a box\n yourself — the human ticks exactly one per question.\n 4. Fold EVERYTHING the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a SKETCH of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n On a RETURN lap the human has ticked one option per question (or\n replaced `_your answer_` with their own text) in\n `<%= it.vars.requirementsFile %>`. For each answered question: fold\n the chosen answer into its concern's prose, then MOVE the question\n down into a `## Answered Questions` section as `### <question>`\n followed by the resolved answer in plain PROSE (drop the\n checkboxes). Never re-raise a question the human deleted; if the\n human deleted the whole `## Open Questions` section, treat every\n product concern as accepted and surface no new questions unless\n something is genuinely still blocking. Omit `## Open Questions`\n entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your\n turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.requirementsFile %>\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `<%= it.vars.requirementsFile %>` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # ── ENCAPSULATION: the technical phase's one conversation ──────────────────\n # A separate machine from `designPlan` — its own memory scope, since there\n # may be a handover between the user-facing and the technical phase: this\n # phase does NOT resume design's conversation, it reads\n # `<%= it.vars.requirementsFile %>` cold. Resolves the TECHNICAL open\n # questions, then mechanically decomposes the settled concerns into package\n # files (no merge/split judgement — that already happened in triage's\n # grouping). ▸ planner identity — `model:` declared once here, stamped onto\n # `author` and `decompose` (its own prompt states; `gate.answer` is a human\n # turn and carries no prompt).\n archPlan:\n model: <%= it.vars.plannerModel %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: <%= it.vars.architectureFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do NOT resume the design conversation — this is a separate\n machine with its own memory, a COLD read every time. Read\n `<%= it.vars.requirementsFile %>` (the settled, ordered, classified\n concerns) in full. Treat every product decision recorded there —\n plain prose or a resolved `## Answered Questions` entry — as\n SETTLED; never re-open it.\n\n On the FIRST lap, develop `<%= it.vars.architectureFile %>` from\n those concerns: for each one, in the same order, work out the *how*\n — file/module structure, data models, library/tech-stack choices,\n error-handling strategy — building on the *what* already settled.\n For every open TECHNICAL point you cannot settle yourself, add a\n question under a `## Open Questions` heading exactly as the\n requirements phase does: one `### <question>` each, followed by a\n checkbox list — TWO concrete options plus a final free-text slot,\n ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one\n option per question (or replaced `_your answer_` with their own\n text): fold each chosen answer into the plan prose, then MOVE the\n question into a `## Answered Questions` section as `### <question>`\n + the resolved answer in plain PROSE (drop the checkboxes). Never\n re-raise a deleted question; treat a deleted `## Open Questions`\n section as acceptance. Omit `## Open Questions` once there are none\n left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in\n and it must not linger. Leave everything uncommitted and finish\n your turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.architectureFile %>\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `<%= it.vars.architectureFile %>` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an ORDERED\n set of concerns with every merge/split judgement made — this turn is\n a mechanical write-out, not a planning one.\n\n Write one package file per concern, in the settled order, under\n `<%= it.vars.packagesDir %>/` (e.g.\n `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Each package file\n carries that concern's requirement (so it can be reviewed against\n its spec), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do NOT merge or\n split concerns here — that judgement already happened upstream;\n carry the settled grouping over verbatim. No package file may\n reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `<%= it.vars.architectureFile %>`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": $onPackages\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). The implementer's OWN\n # follow-through on feedback is a coder action, not a reviewer one — it\n # lives in `build.addressing` (`$onFeedback`), not inside this machine.\n humanReview:\n model: <%= it.vars.plannerModel %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the review-signoff guard (`src/StepGuards.ts`)\n # catches them BEFORE the step commits and refuses: an unfinished\n # review (a box still `- [ ]` with no comment) and a deleted REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to collapse the process into\n one commit (**build.squashing**).\n - **Request changes** — leave a comment: a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review.deciding** → **build.addressing**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (the sign-off gate\n # refuses an incomplete step, so `deciding` is entered exactly once per\n # round — nothing commits in between). It is FEEDBACK when the human left\n # a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\" tick) OR a\n # hand-edit to any file this round other than `.gtd/` or the state's own\n # reviewFile (which is var-configurable and may live outside `.gtd/`, e.g.\n # a root-level REVIEW.md — issue #128). Otherwise it is a clean sign-off.\n # This turn only CAPTURES the raw material into REVIEW_RAW.md (the\n # `collecting` agent then turns it into instructions); it does NOT\n # interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.vars.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n head=$(git rev-parse HEAD)\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it\n # is excluded by exact path, not just the stateDir prefix)?\n if git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v \"^$stateDir/\" | grep -vxF \"$reviewFile\" | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the changes and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions — it names a commit, never inlined content.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in $reviewFile at this commit; their hand edits,\"\n echo \"if any, are in that commit's other paths. Run: git show $head\"\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": collecting\n \"M <%= it.vars.reviewRawFile %>\": collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.reviewRawFile %>` (read it, then\n delete it) and `<%= it.vars.reviewFeedbackFile %>` (write it) — do\n NOT change any other file, you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A <%= it.vars.reviewFeedbackFile %>\": $onFeedback\n \"M <%= it.vars.reviewFeedbackFile %>\": $onFeedback\n\n # ── ENCAPSULATION: the per-package agentic review ──────────────────────────\n # A single state: a machine whose entire job is to be a separate MIND from\n # the implementer, distinct from the coder identity that builds/fixes the\n # package (`packages.item.*`). ▸ planner identity — `model:` declared once\n # here, stamped onto `review` (its only prompt state). Fixing the reviewer's\n # own findings is a coder action, not a reviewer one — it lives in\n # `packages.item.fix-spec` (`$onFix`), not inside this machine.\n specReview:\n model: <%= it.vars.plannerModel %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # Bounded per process: once entered `max` times, a transition into it is\n # redirected to `$onApproved` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `build.fix` cap has (see the README's \"Why\" section, \"Bounded, not\n # runaway\").\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `<%= it.vars.specFeedbackFile %>`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A <%= it.vars.specFeedbackFile %>\": $onFix\n \"M <%= it.vars.specFeedbackFile %>\": $onFix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the per-package build queue ─────────────────────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the design/architecture phases' spent steering files:\n # requirementsFile is consumed by architecture.author, architectureFile\n # by architecture.decompose, and questionsFile by whichever gate ran\n # last — all three are gone by the time the package queue starts.\n # No-op if already removed.\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n questions=\"<%~ it.vars.questionsFile %>\"\n rm -f \"$requirements\" \"$architecture\" \"$questions\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the per-package build identity ──────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `<%= it.vars.satisfiedFile %>` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `<%= it.vars.nextFile %>` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `<%= it.vars.satisfiedFile %>` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A <%= it.vars.satisfiedFile %>\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M <%= it.vars.satisfiedFile %>\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %> <%~ it.vars.satisfiedFile %>\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the process's build identity ─────────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `squashing`,\n # `addressing`, and `fix` (its own prompt states). There is no monolithic\n # \"build the whole plan\" state any more — every package is built inside\n # `packages.item`, and the queue drains straight into `build.review`.\n # `addressing` is the implementer's OWN follow-through on review feedback\n # (a coder action) — reached via `$onFeedback`, bound at its own `review:`\n # reference below, not nested inside the (planner-only) `humanReview`\n # machine. `humanReview` is nested HERE (not at the root) so the review\n # tail's run stays inside the builder's own scope — see the header comment.\n buildTail:\n model: <%= it.vars.coderModel %>\n params: [onDone]\n # Nothing resolves this machine BARE (every reference into it names a\n # specific descendant, e.g. `build.review` / `build.fix`) — but a machine\n # must still declare an `entry:` local per its own grammar, so this names\n # an arbitrary one of its own states.\n entry: fix\n states:\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n prompt: |\n You are an autonomous coding agent. The process is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `<%= it.vars.commitMsgFile %>` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `<%= it.vars.commitMsgFile %>` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A <%= it.vars.commitMsgFile %>\": $onDone\n \"M <%= it.vars.commitMsgFile %>\": $onDone\n\n addressing:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The\n # feedback-progress guard (`src/StepGuards.ts`) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. This\n workflow steers itself through its own state files — treat them as\n its private scratchpad, never as project code or documentation. The\n only state file this turn touches is\n `<%= it.vars.reviewFeedbackFile %>`; address it, then delete it. Do\n not create other files to hold notes or output.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": health.check\n\n fix:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so its run stays\n # inside the builder's own scope across the feedback round-trip — see\n # the header comment. Sign-off -> squashing; feedback -> addressing (the\n # implementer's own coder-identity follow-through, not a reviewer action).\n review:\n machine: humanReview\n with:\n onSignoff: squashing\n onFeedback: addressing\n\n # The root machine — the whole one-flow tree described in this file's\n # header comment. WIRING ONLY: no identity of its own, just the shared\n # entry/exit states and the top-level machine tree.\n unified:\n entry: idle\n states:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n label: Idle\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. gtd treats it as a SKETCH, not finished work:\n the very next beat unwinds it out of your working tree (its intent\n survives in history, for the triage phase to read), then checks the\n test baseline is green, then triages the reverted diff into ordered,\n classified concerns: product questions, then technical questions,\n then one package per concern, built and reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # ── Green-baseline gate: `gtd --entry fix-precheck` entry ─────────────────\n # `gtd --entry fix-precheck` enters HERE: run the suite, then a red run\n # drops straight into the shared `build.fix` loop (-> build.health.check ->\n # review + squash tail); a green run is a no-op back to `idle` (nothing\n # to fix). Aliases the `&suiteCheck` anchor defined on `entryGate.check`'s\n # own `script:` above — the same plain suite check `start-gate`/\n # `review-gate` run.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A <%= it.vars.feedbackFile %>\": build.fix\n \"M <%= it.vars.feedbackFile %>\": build.fix\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Unwind: revert the entry commit's diff before planning starts ──────\n # Kept a SEPARATE state rather than folded into `start-gate.check`'s own\n # script: the `blocked -> check` retry edge re-runs the gate, so a\n # revert living there would discard the human's own baseline repair and\n # would need an idempotence guard. With exactly one inbound edge (from\n # `idle`), `unwind` is structurally reached once per process and needs\n # no guard at all. Names the reverted commit via `it.currentCommit`\n # (they're the same commit here), NOT a bare `HEAD` — pinning the\n # render-time hash keeps the script correct even if a driver runs it\n # late.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env bash\n # gtd check turn — revert the entry commit's diff out of the working\n # tree. Its content survives in history for design.triage to read.\n set +e\n # Hoist the commit hash once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last\n # token on a line.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # ── Green-baseline gate: the single start entry. ───────────────────────\n # Runs on the tree AFTER `unwind`, so this is now just the ordinary\n # green-baseline check `review-gate`/`fix-precheck` also run — no\n # baseline-aware second run, no detached checkout, no HEAD move\n # anywhere in gtd's emitted scripts any more.\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── The triage phase (design.triage -> design.gate.check/.answer -> architecture). ──\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n # ── The technical phase (architecture.author -> architecture.gate.check/.answer ─\n # -> architecture.decompose -> packages). ──────────────────────────────\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n # ── The per-package build queue (packages.* -> build.review). ─────────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── The process's build identity (build.* -> build.review -> done). ───────\n build:\n machine: buildTail\n with:\n onDone: done\n";
|
|
192202
192202
|
//#endregion
|
|
192203
192203
|
//#region src/workflows/templates.ts
|
|
192204
192204
|
/**
|
|
@@ -192228,20 +192228,17 @@ const INIT_VARS = { testCommand: "npm test" };
|
|
|
192228
192228
|
* The top-level `modes:` block `gtd init` seeds into the scaffolded
|
|
192229
192229
|
* `.gtdrc.json` as a ready-to-edit suggestion: a `format:` command for each
|
|
192230
192230
|
* built-in steering-file mode the bundled default uses (`qa` for the
|
|
192231
|
-
* open-questions files, `review` for REVIEW.md,
|
|
192232
|
-
*
|
|
192233
|
-
*
|
|
192234
|
-
*
|
|
192235
|
-
* format-only built-in — see `PatternMachine.isKnownBuiltInMode`) still
|
|
192236
|
-
* validates nothing (the two halves layer independently — see
|
|
192231
|
+
* open-questions files, `review` for REVIEW.md), so a fresh project's
|
|
192232
|
+
* steering files are auto-formatted with Prettier before gtd validates them.
|
|
192233
|
+
* Only `format:` is declared — gtd's built-in `qa`/`review` validators still
|
|
192234
|
+
* do the validating (the two halves layer independently — see
|
|
192237
192235
|
* `src/SteeringMode.ts`). gtd ships no formatter, so this is the one
|
|
192238
192236
|
* place a default is suggested; a project edits or drops it freely (swap
|
|
192239
192237
|
* Prettier for dprint, point at a script, delete the key).
|
|
192240
192238
|
*/
|
|
192241
192239
|
const MODES_SUGGESTION = {
|
|
192242
192240
|
qa: { format: "npx prettier --write <%= it.file %>" },
|
|
192243
|
-
review: { format: "npx prettier --write <%= it.file %>" }
|
|
192244
|
-
prose: { format: "npx prettier --write <%= it.file %>" }
|
|
192241
|
+
review: { format: "npx prettier --write <%= it.file %>" }
|
|
192245
192242
|
};
|
|
192246
192243
|
/**
|
|
192247
192244
|
* The single bundled workflow template — the raw YAML text of `unified.yaml`,
|
|
@@ -193987,8 +193984,7 @@ const restoredOutcome = (to, state) => `gtd_report_restored ${shellQuote(to)} ${
|
|
|
193987
193984
|
*/
|
|
193988
193985
|
const headAssertion = (expectedHead) => {
|
|
193989
193986
|
const q = shellQuote(expectedHead);
|
|
193990
|
-
|
|
193991
|
-
return expectedHead === "" ? probe + "{ printf 'gtd: repository changed since this script was generated (expected a repository with no commits yet) — re-run gtd\\n' >&2; exit 1; }" : probe + `{ printf 'gtd: repository changed since this script was generated (expected HEAD %s) — re-run gtd\\n' ${q} >&2; exit 1; }`;
|
|
193987
|
+
return `[ "$(git rev-parse --verify --quiet HEAD 2>/dev/null)" = ${q} ] || { printf 'gtd: repository changed since this script was generated (expected HEAD %s) — re-run gtd\\n' ${q} >&2; exit 1; }`;
|
|
193992
193988
|
};
|
|
193993
193989
|
/**
|
|
193994
193990
|
* Same shape as `headAssertion`, for the saved-head ref a review window
|
|
@@ -194131,26 +194127,23 @@ const combinedScript = (required, optional) => {
|
|
|
194131
194127
|
* (`src/ReviewWindow.ts` opens/closes around every command; a `Rest` resolved
|
|
194132
194128
|
* before that bracket runs would resolve against the wrong HEAD).
|
|
194133
194129
|
*
|
|
194134
|
-
* `currentRest`/`restAt` resolve the following stages, in order
|
|
194135
|
-
* single `hasCommits()` read through every stage that needs it (today's three
|
|
194136
|
-
* separate calls collapse to one):
|
|
194130
|
+
* `currentRest`/`restAt` resolve the following stages, in order:
|
|
194137
194131
|
*
|
|
194138
194132
|
* 1. `config.load`
|
|
194139
|
-
* 2. `
|
|
194140
|
-
* 3. `
|
|
194141
|
-
* 4. the pure `resolveRestFrom(def, headSubject)` → `ResolvedRest`, or the
|
|
194133
|
+
* 2. `lastCommitSubject(ref)`
|
|
194134
|
+
* 3. the pure `resolveRestFrom(def, headSubject)` → `ResolvedRest`, or the
|
|
194142
194135
|
* renamed-state refusal
|
|
194143
|
-
*
|
|
194144
|
-
*
|
|
194145
|
-
*
|
|
194136
|
+
* 4. `commitHistory()` → `ProcessRun`
|
|
194137
|
+
* 5. `vars` — the four-layer merge (workflow < rc < entry commit < env)
|
|
194138
|
+
* 6. `on` — the resting state's `on` edges, rendered against `vars` (Eta
|
|
194146
194139
|
* templates; `vars` must precede this)
|
|
194147
|
-
*
|
|
194140
|
+
* 7. `stepDef` — `def` with `on` patched onto the resting state, the shape
|
|
194148
194141
|
* `PatternMachine.step` must be fed
|
|
194149
|
-
*
|
|
194150
|
-
*
|
|
194151
|
-
*
|
|
194142
|
+
* 8. `reviewBase` — the pure `reviewBaseFor(def, run)`
|
|
194143
|
+
* 9. `changes` — `changedPaths()`
|
|
194144
|
+
* 10. `context` — the full `TemplateContext` (`on` and `reviewBase` both feed
|
|
194152
194145
|
* it — `it.edges` derives from `on`)
|
|
194153
|
-
*
|
|
194146
|
+
* 11. `memory`, then `hints` (`model`/`label`/`file` rendered against
|
|
194154
194147
|
* `context`; `mode` passed through verbatim)
|
|
194155
194148
|
*
|
|
194156
194149
|
* Two module rules, enforced on this file itself: a private helper with only
|
|
@@ -194247,8 +194240,7 @@ const costByModel = (entries) => {
|
|
|
194247
194240
|
const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
|
|
194248
194241
|
/**
|
|
194249
194242
|
* Resolve a HEAD commit's subject against the active workflow definition —
|
|
194250
|
-
* PURE: no git, no Effect.
|
|
194251
|
-
* yet (the caller supplies it, already read).
|
|
194243
|
+
* PURE: no git, no Effect.
|
|
194252
194244
|
*
|
|
194253
194245
|
* A HEAD subject that DOES parse as a `gtd(actor): state` commit, but whose
|
|
194254
194246
|
* named state the CURRENT workflow definition doesn't declare AT ALL, is
|
|
@@ -194350,8 +194342,7 @@ const parseEntryCommitOverrides = (processCommits) => {
|
|
|
194350
194342
|
* (`trace: []`, `startHash`/`startParentHash` both HEAD's own hash) — the
|
|
194351
194343
|
* same shape a fresh rest at a squashed boundary has always had.
|
|
194352
194344
|
*
|
|
194353
|
-
* `
|
|
194354
|
-
* doc comment's stage ordering) rather than re-read here. `head` — a resolved
|
|
194345
|
+
* `head` — a resolved
|
|
194355
194346
|
* hash, never a symbolic ref — overrides the literal `HEAD` the walk would
|
|
194356
194347
|
* otherwise end at: `restAt`'s window-aware branch passes the review
|
|
194357
194348
|
* checkout window's saved-head hash here while the window is open, so the
|
|
@@ -194360,16 +194351,7 @@ const parseEntryCommitOverrides = (processCommits) => {
|
|
|
194360
194351
|
* exact behavior — an explicit trailing ref is passed to `git.commitHistory`
|
|
194361
194352
|
* only when `head` differs from the literal `"HEAD"` it already defaults to.
|
|
194362
194353
|
*/
|
|
194363
|
-
const computeProcessRun = (git, def,
|
|
194364
|
-
if (!hasCommits) return {
|
|
194365
|
-
startHash: "",
|
|
194366
|
-
startParentHash: EMPTY_TREE$2,
|
|
194367
|
-
diffBase: EMPTY_TREE$2,
|
|
194368
|
-
trace: [],
|
|
194369
|
-
costEntries: [],
|
|
194370
|
-
entryVars: {},
|
|
194371
|
-
headTurn: void 0
|
|
194372
|
-
};
|
|
194354
|
+
const computeProcessRun = (git, def, head) => gen(function* () {
|
|
194373
194355
|
const initialState = initialStateOf(def);
|
|
194374
194356
|
const history = yield* git.commitHistory(void 0, head);
|
|
194375
194357
|
let i = history.length - 1;
|
|
@@ -194414,9 +194396,8 @@ const computeProcessRun = (git, def, hasCommits, head) => gen(function* () {
|
|
|
194414
194396
|
const currentRun = gen(function* () {
|
|
194415
194397
|
const git = yield* GitService;
|
|
194416
194398
|
const config = yield* (yield* ConfigService).load;
|
|
194417
|
-
const hasCommits = yield* git.hasCommits();
|
|
194418
194399
|
const windowHead = getOrUndefined(yield* git.readRefOption(REVIEW_HEAD_REF$1));
|
|
194419
|
-
return yield* computeProcessRun(git, config.workflow,
|
|
194400
|
+
return yield* computeProcessRun(git, config.workflow, windowHead);
|
|
194420
194401
|
});
|
|
194421
194402
|
/**
|
|
194422
194403
|
* PURE: the most-recent in-process turn commit that entered a `reviewBase`
|
|
@@ -194599,13 +194580,11 @@ const withRenderedOn = (def, state, renderedOnEdges) => ({
|
|
|
194599
194580
|
* breakdown including the squashing step) — `0`/absent for the pure emitters
|
|
194600
194581
|
* (`gtd next`/`gtd status`), where no step is being performed. No diff is ever
|
|
194601
194582
|
* computed here — `it.reviewBase`/`it.retainedBase` are bases a template tells
|
|
194602
|
-
* the agent to `git diff` itself.
|
|
194603
|
-
* (already read once — see the module doc comment's stage ordering) rather
|
|
194604
|
-
* than re-read here.
|
|
194583
|
+
* the agent to `git diff` itself.
|
|
194605
194584
|
*/
|
|
194606
|
-
const buildTemplateContext = (git, read, state, actor, run, vars, edges, currentCost, currentModel, reviewBase
|
|
194607
|
-
const currentCommit =
|
|
194608
|
-
const previousCommit =
|
|
194585
|
+
const buildTemplateContext = (git, read, state, actor, run, vars, edges, currentCost, currentModel, reviewBase) => gen(function* () {
|
|
194586
|
+
const currentCommit = yield* git.resolveRef("HEAD");
|
|
194587
|
+
const previousCommit = yield* git.resolveRef("HEAD~1").pipe(catchAll(() => succeed$3(run.startParentHash)));
|
|
194609
194588
|
const stepEntry = currentCost > 0 || currentModel !== void 0 ? [{
|
|
194610
194589
|
cost: currentCost,
|
|
194611
194590
|
model: currentModel ?? "unspecified"
|
|
@@ -194640,7 +194619,7 @@ const omitUndefined = (obj) => Object.fromEntries(Object.entries(obj).filter(([,
|
|
|
194640
194619
|
* Resolve `Rest` at an arbitrary ref (`gtd visualize` reading the review
|
|
194641
194620
|
* window's saved head via `REVIEW_HEAD_REF`, or `undefined` for HEAD itself —
|
|
194642
194621
|
* see `currentRest`). Assembles every stage documented in the module doc
|
|
194643
|
-
* comment
|
|
194622
|
+
* comment.
|
|
194644
194623
|
*
|
|
194645
194624
|
* Window-aware ONLY at `ref === undefined` (`currentRest`'s own case): when
|
|
194646
194625
|
* the review checkout window's saved-head ref (`REVIEW_HEAD_REF`) resolves,
|
|
@@ -194662,21 +194641,20 @@ const restAt = (ref) => gen(function* () {
|
|
|
194662
194641
|
const files = yield* RepoFiles;
|
|
194663
194642
|
const envVars = yield* EnvVars;
|
|
194664
194643
|
const def = config.workflow;
|
|
194665
|
-
const hasCommits = yield* git.hasCommits();
|
|
194666
194644
|
let windowHead;
|
|
194667
194645
|
if (ref === void 0) windowHead = getOrUndefined(yield* git.readRefOption(REVIEW_HEAD_REF$1));
|
|
194668
194646
|
const effectiveRef = windowHead ?? ref;
|
|
194669
|
-
const headSubject =
|
|
194647
|
+
const headSubject = yield* git.lastCommitSubject(effectiveRef);
|
|
194670
194648
|
const resolution = resolveRestFrom(def, headSubject);
|
|
194671
194649
|
if (!resolution.ok) return yield* fail$2(resolution.error);
|
|
194672
194650
|
const resolved = resolution.rest;
|
|
194673
|
-
const run = yield* computeProcessRun(git, def,
|
|
194651
|
+
const run = yield* computeProcessRun(git, def, windowHead);
|
|
194674
194652
|
const vars = resolveVars(config.workflowVars, config.rcVars, run.entryVars, envVars.all);
|
|
194675
194653
|
const on = yield* renderOnEdgesOrFail(resolved.stateDef.on, vars);
|
|
194676
194654
|
const stepDef = withRenderedOn(def, resolved.state, on);
|
|
194677
194655
|
const reviewBase = reviewBaseFor(def, run);
|
|
194678
194656
|
const changes = yield* pendingChanges(git, windowHead);
|
|
194679
|
-
const context = yield* buildTemplateContext(git, templateRead(files), resolved.state, resolved.actor, run, vars, on, 0, void 0, reviewBase
|
|
194657
|
+
const context = yield* buildTemplateContext(git, templateRead(files), resolved.state, resolved.actor, run, vars, on, 0, void 0, reviewBase);
|
|
194680
194658
|
const memory = memoryKeyFor(config.stateScopes, resolved, run);
|
|
194681
194659
|
const memoryResumed = memoryResumedFor(def, config.stateScopes, resolved, run);
|
|
194682
194660
|
const hints = yield* renderHints(resolved.stateDef, context);
|
|
@@ -194795,8 +194773,7 @@ const contextAt = (rest, targetState, invoker, cost, model) => gen(function* ()
|
|
|
194795
194773
|
const git = yield* GitService;
|
|
194796
194774
|
const files = yield* RepoFiles;
|
|
194797
194775
|
const onEdges = targetState === rest.state ? rest.on : yield* renderOnEdgesOrFail(rest.def.states[targetState]?.on, rest.vars);
|
|
194798
|
-
|
|
194799
|
-
return yield* buildTemplateContext(git, templateRead(files), targetState, invoker, rest.run, rest.vars, onEdges, cost ?? 0, model, reviewBaseFor(rest.def, rest.run), hasCommits);
|
|
194776
|
+
return yield* buildTemplateContext(git, templateRead(files), targetState, invoker, rest.run, rest.vars, onEdges, cost ?? 0, model, reviewBaseFor(rest.def, rest.run));
|
|
194800
194777
|
});
|
|
194801
194778
|
/**
|
|
194802
194779
|
* Render a `"commit"`/`"squash"` decision as the `EmitStep`s the external
|
|
@@ -194928,9 +194905,8 @@ const renderDecision = (git, rest, decision, context, cost, model) => gen(functi
|
|
|
194928
194905
|
/**
|
|
194929
194906
|
* The `EmitPreconditions` a step's/entry's assembled scripts assert against:
|
|
194930
194907
|
* `expectedHead` is the resolved HEAD hash the `Rest` snapshot was already
|
|
194931
|
-
* taken at — `rest.context.currentCommit`, passed straight through
|
|
194932
|
-
*
|
|
194933
|
-
* now understands natively). When `targetState` declares `reviewWindow: true`
|
|
194908
|
+
* taken at — `rest.context.currentCommit`, passed straight through. When
|
|
194909
|
+
* `targetState` declares `reviewWindow: true`
|
|
194934
194910
|
* AND a window is currently open (`REVIEW_HEAD_REF` resolves), the script also
|
|
194935
194911
|
* pins the window's saved-head hash — a later run whose window has since
|
|
194936
194912
|
* closed or moved must re-decide rather than trust a stale script. No window,
|
|
@@ -206412,9 +206388,9 @@ const renderSteeringCommands = (resolved, file, context) => gen(function* () {
|
|
|
206412
206388
|
* config at all) falls back to today's basename dispatch (`REVIEW.md` →
|
|
206413
206389
|
* `review`, via `resolveBuiltInMode`), so the server still works standalone
|
|
206414
206390
|
* with no `.gtdrc` in sight. (`.gtd/TODO.md` is NOT dispatched by basename —
|
|
206415
|
-
*
|
|
206416
|
-
*
|
|
206417
|
-
*
|
|
206391
|
+
* no bundled state writes there at all; a custom workflow that wants qa
|
|
206392
|
+
* validation on TODO.md declares it with `file:`+`mode: qa`, which the
|
|
206393
|
+
* config-driven map covers.)
|
|
206418
206394
|
*
|
|
206419
206395
|
* A mode's `validate:` command displacing a built-in format's own parser (a
|
|
206420
206396
|
* `modes: { qa: { validate: "…" } }` override) still gets outline + actions —
|
|
@@ -206514,7 +206490,7 @@ const diagnosticsFor = (resolved, content) => {
|
|
|
206514
206490
|
if (caps.externalValidate === true && resolved?.validate?.kind === "command") return [externalValidatorNotice(resolved.mode, resolved.validate.command)];
|
|
206515
206491
|
return [];
|
|
206516
206492
|
};
|
|
206517
|
-
/** The basename dispatch this server has always had — the fallback for any path the active workflow's `file:` map doesn't cover (or when no config resolves at all). `TODO.md` is intentionally NOT mapped:
|
|
206493
|
+
/** The basename dispatch this server has always had — the fallback for any path the active workflow's `file:` map doesn't cover (or when no config resolves at all). `TODO.md` is intentionally NOT mapped: no bundled state writes there at all. */
|
|
206518
206494
|
const basenameFallbackMode = (name) => name === "REVIEW.md" ? resolveBuiltInMode("review") : void 0;
|
|
206519
206495
|
/**
|
|
206520
206496
|
* Render every state's declared `file:`/`mode:` pair into an absolute-path →
|
|
@@ -207564,7 +207540,7 @@ const runInitCommand = (json, write) => gen(function* () {
|
|
|
207564
207540
|
/** `gtd land`'s exit code for the two SETTLED shapes (`LandResult.settled`) — nothing owed, but stdout still carries a script a piping driver must run. Every other successful landing exits 0. */
|
|
207565
207541
|
const EXIT_SETTLED = 3;
|
|
207566
207542
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
207567
|
-
/** The `EmitPreconditions` every script this file assembles asserts against: the resolved HEAD hash the snapshot driving it was taken at
|
|
207543
|
+
/** The `EmitPreconditions` every script this file assembles asserts against: the resolved HEAD hash the snapshot driving it was taken at. */
|
|
207568
207544
|
const headPreconditions = (currentCommit) => ({ expectedHead: currentCommit });
|
|
207569
207545
|
/**
|
|
207570
207546
|
* The resting state's own steering-mode commands, embedded ahead of the
|
|
@@ -208159,10 +208135,23 @@ const runValidateCommand = (json, write) => gen(function* () {
|
|
|
208159
208135
|
* `{valid,errors}`"): the alternative was piping a `--json` failure through a
|
|
208160
208136
|
* NEW exit-code channel bypassing `Cli.ts`'s shared envelope entirely, which
|
|
208161
208137
|
* every other command's `--json` failure goes through today.
|
|
208138
|
+
*
|
|
208139
|
+
* `--open-questions` (`openQuestions: true`) replaces this whole
|
|
208140
|
+
* structural-findings path with `runOpenQuestionsCheckCommand`, below — but
|
|
208141
|
+
* only after the SAME unknown-mode validation runs (a typo'd mode must still
|
|
208142
|
+
* fail loudly, not silently pass an answered-looking document), and only for
|
|
208143
|
+
* `mode === "qa"` (the only mode the open-questions predicate means anything
|
|
208144
|
+
* for; `<mode>` stays a required positional because the arity table always
|
|
208145
|
+
* takes two, but `"review"` there is a distinct, equally wrong, silent
|
|
208146
|
+
* mismatch this rejects explicitly rather than accepting).
|
|
208162
208147
|
*/
|
|
208163
|
-
const runCheckCommand = (mode, file, json, write) => gen(function* () {
|
|
208148
|
+
const runCheckCommand = (mode, file, openQuestions, json, write) => gen(function* () {
|
|
208164
208149
|
const format = steeringFormatFor(mode);
|
|
208165
208150
|
if (format === void 0) return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: unknown mode "${mode}" — known modes: ${builtInModeNames().join(", ")}`));
|
|
208151
|
+
if (openQuestions) {
|
|
208152
|
+
if (mode !== "qa") return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: --open-questions only applies to mode "qa" — got "${mode}"`));
|
|
208153
|
+
return yield* runOpenQuestionsCheckCommand(file, json, write);
|
|
208154
|
+
}
|
|
208166
208155
|
const fs = yield* FileSystem;
|
|
208167
208156
|
const toError = (e) => e instanceof Error ? e : new Error(String(e));
|
|
208168
208157
|
const errors = (yield* fs.exists(file).pipe(mapError$1(toError))) ? format.validate(yield* fs.readFileString(file).pipe(mapError$1(toError))) : [];
|
|
@@ -208180,6 +208169,44 @@ const runCheckCommand = (mode, file, json, write) => gen(function* () {
|
|
|
208180
208169
|
else write(errors.join("\n") + "\n");
|
|
208181
208170
|
return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: ${file} is not valid under mode "${mode}" (${errors.length} finding(s))`));
|
|
208182
208171
|
});
|
|
208172
|
+
/**
|
|
208173
|
+
* `gtd check <mode> <file> --open-questions`: read `<file>` and run
|
|
208174
|
+
* `OpenQuestions.ts`'s `unansweredQuestions` — the SAME predicate
|
|
208175
|
+
* `StepGuards.ts`'s answer-completeness guard enforces at land — printing one
|
|
208176
|
+
* unanswered question per line and exiting non-zero when any remain. Sharing
|
|
208177
|
+
* the one function makes a workflow's own gate script (this) and the land-time
|
|
208178
|
+
* guard (`answerCompletenessGuard`) two views of the same decision: no
|
|
208179
|
+
* reachable state can skip the gate while a land would still be refused, or
|
|
208180
|
+
* enter the gate while every land would already pass.
|
|
208181
|
+
*
|
|
208182
|
+
* A missing or unreadable file is a non-zero exit carrying a message, mirrored
|
|
208183
|
+
* from `gtd check`'s general "stop and show the human" convention — unlike the
|
|
208184
|
+
* structural path above, which treats an absent file as "nothing to report".
|
|
208185
|
+
* `--json` mirrors the structural branch's own shape exactly (each unanswered
|
|
208186
|
+
* question's text as an `errors` entry): `{valid:true,errors:[]}` on none
|
|
208187
|
+
* remaining, else `{valid:false,errors:[...]}` written before the Effect
|
|
208188
|
+
* fails — the SAME line-delimited-JSON tension `runCheckCommand`'s own doc
|
|
208189
|
+
* comment spells out, not a second shape a `--json` consumer has to learn.
|
|
208190
|
+
*/
|
|
208191
|
+
const runOpenQuestionsCheckCommand = (file, json, write) => gen(function* () {
|
|
208192
|
+
const fs = yield* FileSystem;
|
|
208193
|
+
const toError = (e) => e instanceof Error ? e : new Error(String(e));
|
|
208194
|
+
if (!(yield* fs.exists(file).pipe(mapError$1(toError)))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: ${file} does not exist`));
|
|
208195
|
+
const errors = unansweredQuestions(yield* fs.readFileString(file).pipe(mapError$1(toError))).map((q) => q.question);
|
|
208196
|
+
if (errors.length === 0) {
|
|
208197
|
+
if (json) write(JSON.stringify({
|
|
208198
|
+
valid: true,
|
|
208199
|
+
errors: []
|
|
208200
|
+
}) + "\n");
|
|
208201
|
+
return;
|
|
208202
|
+
}
|
|
208203
|
+
if (json) write(JSON.stringify({
|
|
208204
|
+
valid: false,
|
|
208205
|
+
errors
|
|
208206
|
+
}) + "\n");
|
|
208207
|
+
else write(errors.join("\n") + "\n");
|
|
208208
|
+
return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: ${errors.length} open question(s) unanswered in ${file}`));
|
|
208209
|
+
});
|
|
208183
208210
|
/** Which declared `on` pattern (if any) each pending change matches — the pure computation `gtd status` reports (both plain and `--json`). `onEdges` is ALREADY RENDERED against `it.vars` (`renderOnEdges`) — the reported pattern is the one a real `gtd land` would match against. */
|
|
208184
208211
|
const computeStatusChanges = (onEdges, changes) => changes.map((change) => {
|
|
208185
208212
|
const matchedRow = onEdges.find(([patternStr]) => {
|
|
@@ -208357,6 +208384,20 @@ const assertRunningFromRepoRoot = (git, fs) => gen(function* () {
|
|
|
208357
208384
|
if ((yield* fs.realPath(topLevel)) !== (yield* fs.realPath(process.cwd()))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd must be run from the repository root (${topLevel}); the current directory is ${process.cwd()}`));
|
|
208358
208385
|
});
|
|
208359
208386
|
/**
|
|
208387
|
+
* A command that derives workflow state needs not just a repository root
|
|
208388
|
+
* (`assertRunningFromRepoRoot`) but at least one commit to derive that state
|
|
208389
|
+
* FROM — a commitless repository has no HEAD to resolve a `Rest` at, and the
|
|
208390
|
+
* sketch lookup that later depends on a resolvable base commit would otherwise
|
|
208391
|
+
* fail silently, well past the point where a script has already been emitted.
|
|
208392
|
+
* `git.hasCommits()` is already on the `GitOperations` port and already
|
|
208393
|
+
* contract-tested against both the real-git and in-memory tiers
|
|
208394
|
+
* (`src/testing/GitTiers.ts`'s `hasCommits` group) — this is a single read of
|
|
208395
|
+
* it, plus the failure.
|
|
208396
|
+
*/
|
|
208397
|
+
const assertRepositoryHasCommits = (git) => gen(function* () {
|
|
208398
|
+
if (!(yield* git.hasCommits())) return yield* fail$2(/* @__PURE__ */ new Error("gtd requires a repository with at least one commit — make an initial commit, then run gtd again"));
|
|
208399
|
+
});
|
|
208400
|
+
/**
|
|
208360
208401
|
* `gtd init` writes only a `.gtdrc.json` (+ prompt files) — it derives no state,
|
|
208361
208402
|
* so unlike the state commands it need not sit in a git repository at all. It
|
|
208362
208403
|
* may run EITHER at a repository root OR in a directory outside any repository —
|
|
@@ -208381,6 +208422,10 @@ const assertInitLocation = (git, fs) => gen(function* () {
|
|
|
208381
208422
|
* `Cli.ts` already has a real (value) dependency on this module for
|
|
208382
208423
|
* `runCommand` itself — a value import running the other way would make the
|
|
208383
208424
|
* two modules circular.
|
|
208425
|
+
*
|
|
208426
|
+
* `"state"` means the kind derives workflow state, so it needs a repository
|
|
208427
|
+
* root AND at least one commit to derive that state from — both guards run in
|
|
208428
|
+
* `runCommand`, below, before dispatch.
|
|
208384
208429
|
*/
|
|
208385
208430
|
const needsOf = (kind) => {
|
|
208386
208431
|
switch (kind) {
|
|
@@ -208404,7 +208449,7 @@ const dispatchVoidCommand = (command, json, write) => {
|
|
|
208404
208449
|
case "next": return runNextCommand(json, write);
|
|
208405
208450
|
case "status": return runStatusCommand(json, write);
|
|
208406
208451
|
case "validate": return runValidateCommand(json, write);
|
|
208407
|
-
case "check": return runCheckCommand(command.mode, command.file, json, write);
|
|
208452
|
+
case "check": return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, json, write);
|
|
208408
208453
|
case "install": return runInstallCommand(json, write);
|
|
208409
208454
|
}
|
|
208410
208455
|
};
|
|
@@ -208425,12 +208470,18 @@ const dispatchCommand = (command, json, write) => {
|
|
|
208425
208470
|
/**
|
|
208426
208471
|
* The one entry point `Cli.ts`'s `runCli` calls for a resolved `Command`:
|
|
208427
208472
|
* dispatches to the matching `run*Command` handler (see `dispatchCommand`),
|
|
208428
|
-
* wrapped in the repo-root guard exactly when
|
|
208429
|
-
* "state"` — `lsp`/`init`/`visualize`/`check` (the
|
|
208430
|
-
* bare. `Cli.ts` has already validated every field on
|
|
208431
|
-
* scope, decoding), so nothing here re-parses argv.
|
|
208432
|
-
* `Cli.ts`'s `runCli` should use — `0` for every
|
|
208433
|
-
* settles (`EXIT_SETTLED`).
|
|
208473
|
+
* wrapped in the repo-root-and-commit guard exactly when
|
|
208474
|
+
* `needsOf(command.kind) === "state"` — `lsp`/`init`/`visualize`/`check` (the
|
|
208475
|
+
* `standaloneKinds`) run bare. `Cli.ts` has already validated every field on
|
|
208476
|
+
* `command` (arity, flag scope, decoding), so nothing here re-parses argv.
|
|
208477
|
+
* Returns the exit code `Cli.ts`'s `runCli` should use — `0` for every
|
|
208478
|
+
* command except a `land` that settles (`EXIT_SETTLED`).
|
|
208479
|
+
*
|
|
208480
|
+
* The guard is two checks, in order: `assertRunningFromRepoRoot` (running from
|
|
208481
|
+
* the wrong directory is the more fundamental misuse, and its message is about
|
|
208482
|
+
* WHERE you are), then `assertRepositoryHasCommits` (a repository with no
|
|
208483
|
+
* commits has no HEAD to derive workflow state from). Both run before
|
|
208484
|
+
* `dispatch`, so a refusal writes nothing and emits no script by construction.
|
|
208434
208485
|
*
|
|
208435
208486
|
* No review-window close/open bracket runs here any more: every state-command
|
|
208436
208487
|
* handler (see `planLanding`) now decides for itself, off `ReviewWindow.ts`'s
|
|
@@ -208445,6 +208496,7 @@ const runCommand = (command, json, write) => {
|
|
|
208445
208496
|
const git = yield* GitService;
|
|
208446
208497
|
const fs = yield* FileSystem;
|
|
208447
208498
|
yield* assertRunningFromRepoRoot(git, fs);
|
|
208499
|
+
yield* assertRepositoryHasCommits(git);
|
|
208448
208500
|
return yield* dispatch;
|
|
208449
208501
|
});
|
|
208450
208502
|
};
|
|
@@ -208561,6 +208613,21 @@ const FLAGS = [
|
|
|
208561
208613
|
"override for the new process; the name must already be",
|
|
208562
208614
|
"declared by the workflow's own vars: or the .gtdrc vars:"
|
|
208563
208615
|
]
|
|
208616
|
+
},
|
|
208617
|
+
{
|
|
208618
|
+
name: "--open-questions",
|
|
208619
|
+
arity: 0,
|
|
208620
|
+
repeatable: false,
|
|
208621
|
+
scope: (kind) => kind === "check",
|
|
208622
|
+
decode: () => right(true),
|
|
208623
|
+
scopeError: "gtd: --open-questions is only valid for `gtd check`",
|
|
208624
|
+
valueHint: "",
|
|
208625
|
+
help: [
|
|
208626
|
+
"(gtd check only) ignore <mode>'s structural findings and",
|
|
208627
|
+
"instead run the qa open-questions predicate over <file>,",
|
|
208628
|
+
"printing each unanswered question one per line and exiting",
|
|
208629
|
+
"non-zero when any remain"
|
|
208630
|
+
]
|
|
208564
208631
|
}
|
|
208565
208632
|
];
|
|
208566
208633
|
const flagByToken = (token) => {
|
|
@@ -208692,7 +208759,9 @@ const COMMAND_ROWS = [
|
|
|
208692
208759
|
"non-zero when there are any. Resolves no workflow state and",
|
|
208693
208760
|
"reads no config — standalone, runnable from any directory",
|
|
208694
208761
|
"with <mode>/<file> given explicitly. This is what a",
|
|
208695
|
-
"workflow's emitted validation script invokes as a leaf step"
|
|
208762
|
+
"workflow's emitted validation script invokes as a leaf step.",
|
|
208763
|
+
"--open-questions runs the qa unanswered-questions predicate",
|
|
208764
|
+
"instead (see --help)"
|
|
208696
208765
|
]
|
|
208697
208766
|
},
|
|
208698
208767
|
{
|
|
@@ -208949,7 +209018,8 @@ const parseArgv = (argv) => {
|
|
|
208949
209018
|
command: {
|
|
208950
209019
|
kind: "check",
|
|
208951
209020
|
mode: restPositionals[0],
|
|
208952
|
-
file: restPositionals[1]
|
|
209021
|
+
file: restPositionals[1],
|
|
209022
|
+
...bag["--open-questions"] !== void 0 ? { openQuestions: bag["--open-questions"] } : {}
|
|
208953
209023
|
},
|
|
208954
209024
|
json
|
|
208955
209025
|
};
|