@pmelab/gtd 7.4.1 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/bin/gtd +137 -18
- package/dist/gtd.bundle.mjs +142 -51
- package/docs/cli.md +66 -6
- package/docs/configuration.md +27 -14
- package/docs/loop.md +37 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,6 +106,9 @@ handful of commands drive it:
|
|
|
106
106
|
pending changes, and which action leads where. The panel and diagram highlight
|
|
107
107
|
refresh live (~every 3s) while the page is open, so advancing the process
|
|
108
108
|
elsewhere shows up without a manual refresh.
|
|
109
|
+
- **`gtd edit [path]`** — open `<path>` (or, with no argument, the current
|
|
110
|
+
resting state's steering file, or the repo dir) in `${VISUAL:-$EDITOR}`,
|
|
111
|
+
blocking until it exits.
|
|
109
112
|
|
|
110
113
|
`gtd version` (or `gtd --version`/`-v`) prints the installed version and exits;
|
|
111
114
|
`gtd help` (or `gtd --help`/`-h`) prints the command list. Both short-circuit
|
|
@@ -165,14 +168,20 @@ workflow is just `.gtdrc` config — edit it or write your own (see
|
|
|
165
168
|
through two `vars` tiers — `plannerModel` (heavier planning and review) and
|
|
166
169
|
`coderModel` (the coding turns) — so you can repoint the models globally in one
|
|
167
170
|
place (a `vars:` edit or a `GTD_VAR_plannerModel` override) instead of per
|
|
168
|
-
state.
|
|
171
|
+
state. Steering-file path vars (`feedbackFile`, `reviewFile`, …) work the same
|
|
172
|
+
way, and now propagate to the `on` patterns that route on them too — a repointed
|
|
173
|
+
path var actually reroutes the machine, not just the templates that read/write
|
|
174
|
+
the file.
|
|
169
175
|
|
|
170
176
|
Bare `gtd` (or `gtd loop`) is a ready-to-run driver for the whole protocol —
|
|
171
177
|
point it at a repo and it runs the loop until it's your turn. It is the only
|
|
172
|
-
command you run: at a gate
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
command you run: at a human gate it now opens `${VISUAL:-$EDITOR}` on the gate's
|
|
179
|
+
file for you automatically (answer a plan question, tick a review box, fix
|
|
180
|
+
code), waits for you to save and exit, then captures your edit itself and keeps
|
|
181
|
+
driving — or halts if you left nothing changed — so you never run
|
|
182
|
+
`gtd step human` by hand. Pass `--no-edit` (or set `GTD_NO_EDIT`) to fall back
|
|
183
|
+
to halting and printing the gate instead, if you'd rather edit and re-launch it
|
|
184
|
+
yourself. See [Driving the loop](docs/loop.md).
|
|
176
185
|
|
|
177
186
|
Before wiring gtd into a repo, note the
|
|
178
187
|
[repository requirements](docs/cli.md#repository-requirements) — most
|
package/bin/gtd
CHANGED
|
@@ -3,9 +3,17 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
# This file is the single published `bin` entry: `bin/gtd`. Before anything
|
|
5
5
|
# else, it dispatches on $1:
|
|
6
|
+
# - `--no-edit` (bare, or immediately after `loop`) -> stripped here and
|
|
7
|
+
# disables the loop's automatic editor launching for this run (see
|
|
8
|
+
# `edit_disabled`/`GTD_NO_EDIT` below); dispatch then continues on the
|
|
9
|
+
# remaining arguments as if it had never been given.
|
|
10
|
+
# - `edit [path]` -> handled entirely here in bash, never forwarded to the
|
|
11
|
+
# bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
|
|
12
|
+
# resolved rest's `.file` (or `.` when absent).
|
|
6
13
|
# - no arguments, or exactly `loop` with no further arguments -> fall through
|
|
7
14
|
# into the loop body below (the loop protocol comment further down).
|
|
8
|
-
# - `loop` with additional arguments
|
|
15
|
+
# - `loop` with additional arguments (other than a lone `--no-edit`) ->
|
|
16
|
+
# usage error, exit 2.
|
|
9
17
|
# - anything else -> hand off to the bundle via `run_gtd`, forwarding all
|
|
10
18
|
# arguments unchanged (e.g. `bin/gtd status`, `bin/gtd step human`).
|
|
11
19
|
# `run_gtd` is the ONE place that knows how to invoke "the bundle": by default
|
|
@@ -25,6 +33,55 @@ run_gtd() {
|
|
|
25
33
|
fi
|
|
26
34
|
}
|
|
27
35
|
|
|
36
|
+
# Shared editor-launch helper — used by both the `edit` subcommand below and
|
|
37
|
+
# the loop's human-gate flow further down. Resolves the editor git-style
|
|
38
|
+
# ($VISUAL, then $EDITOR — no fallback to `vi`), then launches it blocking in
|
|
39
|
+
# the foreground with the target path passed as a positional parameter
|
|
40
|
+
# (never string-spliced into the command), so a multi-word command
|
|
41
|
+
# (`code --wait`) and a path with special characters both work. A launch
|
|
42
|
+
# failure (bad/missing editor binary) is a plain non-zero return, propagating
|
|
43
|
+
# under `set -e`; the editor's own exit code is otherwise ignored entirely —
|
|
44
|
+
# the outcome is judged from tree state afterward, not from this function.
|
|
45
|
+
launch_editor() {
|
|
46
|
+
local target="$1" editor="${VISUAL:-${EDITOR:-}}"
|
|
47
|
+
if [[ -z "$editor" ]]; then
|
|
48
|
+
echo "gtd: no editor configured — set \$EDITOR (or \$VISUAL)" >&2
|
|
49
|
+
return 1
|
|
50
|
+
fi
|
|
51
|
+
bash -c "$editor \"\$1\"" gtd-edit "$target"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# --no-edit / GTD_NO_EDIT govern only the loop's automatic editor launching at
|
|
55
|
+
# human gates (see handle_human_gate further down) — `gtd edit` itself never
|
|
56
|
+
# reads either. `--no-edit` is recognised in exactly the two sanctioned
|
|
57
|
+
# positions (bare, or right after `loop`) and stripped before anything below
|
|
58
|
+
# looks at "$@", so the bundle's own unknown-option guard never sees it.
|
|
59
|
+
edit_disabled=0
|
|
60
|
+
[[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
|
|
61
|
+
if [[ "${1:-}" == "--no-edit" && $# -eq 1 ]]; then
|
|
62
|
+
edit_disabled=1
|
|
63
|
+
shift
|
|
64
|
+
elif [[ "${1:-}" == "loop" && "${2:-}" == "--no-edit" && $# -eq 2 ]]; then
|
|
65
|
+
edit_disabled=1
|
|
66
|
+
set -- loop
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ "${1:-}" == "edit" ]]; then
|
|
70
|
+
shift
|
|
71
|
+
if [[ $# -gt 0 ]]; then
|
|
72
|
+
launch_editor "$1"
|
|
73
|
+
else
|
|
74
|
+
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
75
|
+
echo "gtd edit: could not determine the next step:" >&2
|
|
76
|
+
echo "$peek_json" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
file="$(jq -r '.file // empty' <<<"$peek_json")"
|
|
80
|
+
launch_editor "${file:-.}"
|
|
81
|
+
fi
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
28
85
|
if [[ $# -gt 0 && "$1" != "loop" ]]; then
|
|
29
86
|
run_gtd "$@"
|
|
30
87
|
exit $?
|
|
@@ -33,12 +90,16 @@ if [[ "${1:-}" == "loop" && $# -gt 1 ]]; then
|
|
|
33
90
|
echo "gtd: 'loop' takes no arguments" >&2
|
|
34
91
|
exit 2
|
|
35
92
|
fi
|
|
36
|
-
# otherwise ($# -eq 0, or exactly "loop" with no further args
|
|
37
|
-
# into the existing loop body below,
|
|
93
|
+
# otherwise ($# -eq 0, or exactly "loop" with no further args, --no-edit
|
|
94
|
+
# already stripped above): fall through into the existing loop body below,
|
|
95
|
+
# unchanged in behavior.
|
|
38
96
|
|
|
39
97
|
# Drives the v3 pattern-machine loop (see docs/loop.md): each iteration asks
|
|
40
98
|
# `gtd next --json` who's up and what they should do, dispatching on `kind`:
|
|
41
|
-
# - "message" (a human rest) ->
|
|
99
|
+
# - "message" (a human rest) -> see handle_human_gate above: with editing on
|
|
100
|
+
# (the default) open the editor at the gate and step human, driving on a
|
|
101
|
+
# captured commit; with editing off (--no-edit/GTD_NO_EDIT), halt and tell
|
|
102
|
+
# the user what gtd is waiting on, unchanged from before this feature.
|
|
42
103
|
# - "script" (a check rest) -> execute `content` (the emitted wrapper
|
|
43
104
|
# script) verbatim via bash — exit code deliberately ignored, the outcome
|
|
44
105
|
# lives in the tree — then `gtd step <actor>` to capture it; zero new
|
|
@@ -101,6 +162,57 @@ herdr_release() { # hand the pane back to Herdr's normal detection
|
|
|
101
162
|
herdr_run pane release-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd
|
|
102
163
|
}
|
|
103
164
|
|
|
165
|
+
# Shared human-gate handler for a "message" (human) rest — used both by the
|
|
166
|
+
# opening move (when editing is on) and by every "message" rest the main loop
|
|
167
|
+
# encounters. $1 is the rest's `next --json` blob.
|
|
168
|
+
#
|
|
169
|
+
# Editing OFF ($edit_disabled): today's behavior, unchanged — print the gate
|
|
170
|
+
# and exit 0 without touching an editor.
|
|
171
|
+
#
|
|
172
|
+
# Editing ON (the default): open the editor on the rest's `.file` (or `.` when
|
|
173
|
+
# absent), wait for it to exit, then run `gtd step human`:
|
|
174
|
+
# - a NEW commit was authored -> return normally so the caller keeps driving.
|
|
175
|
+
# - ZERO commits were authored -> halt, exit 0 (closing the editor without a
|
|
176
|
+
# capturable change means "done for now" — and keeps a gate with no `C`
|
|
177
|
+
# pattern, e.g. idle, from being reopened forever).
|
|
178
|
+
# - the step REFUSES (non-zero exit: malformed steering file, unfinished
|
|
179
|
+
# review) -> surface it verbatim and halt, exit non-zero — the same
|
|
180
|
+
# wording an opening-move refusal already used before this function
|
|
181
|
+
# existed.
|
|
182
|
+
handle_human_gate() {
|
|
183
|
+
local json="$1" file label state step_out head_before head_after
|
|
184
|
+
label="$(jq -r '.label // .state' <<<"$json")"
|
|
185
|
+
state="$(jq -r .state <<<"$json")"
|
|
186
|
+
|
|
187
|
+
if [[ "$edit_disabled" == 1 ]]; then
|
|
188
|
+
herdr_report blocked "$label"
|
|
189
|
+
herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
|
|
190
|
+
echo "--- Your turn ($state) ---"
|
|
191
|
+
run_gtd next
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
file="$(jq -r '.file // empty' <<<"$json")"
|
|
196
|
+
herdr_report working "$label"
|
|
197
|
+
launch_editor "${file:-.}"
|
|
198
|
+
|
|
199
|
+
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
200
|
+
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
201
|
+
echo "gtd-loop: could not capture your changes at the human gate:" >&2
|
|
202
|
+
echo "$step_out" >&2
|
|
203
|
+
echo "Fix the reported issue and re-run gtd-loop." >&2
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
207
|
+
if [[ "$head_before" == "$head_after" ]]; then
|
|
208
|
+
herdr_report idle "$label"
|
|
209
|
+
herdr_release
|
|
210
|
+
echo "--- Done for now ($state: nothing captured) ---"
|
|
211
|
+
exit 0
|
|
212
|
+
fi
|
|
213
|
+
# else: a new commit was captured — return normally, caller keeps driving.
|
|
214
|
+
}
|
|
215
|
+
|
|
104
216
|
trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
105
217
|
herdr_report blocked "${GTD_LAST_LABEL:-stopped}: exited $code"
|
|
106
218
|
herdr_notify "gtd stopped — $(basename "$PWD")" "${GTD_LAST_LABEL:-loop} failed (exit $code)"
|
|
@@ -154,6 +266,12 @@ run_agent_turn() {
|
|
|
154
266
|
# a harmless no-op; a genuine refusal there (a malformed steering file, or an
|
|
155
267
|
# edit no `on` pattern accepts) is surfaced and stops the loop rather than being
|
|
156
268
|
# driven past.
|
|
269
|
+
#
|
|
270
|
+
# Editing ON: route through the exact same gate logic the main loop uses below
|
|
271
|
+
# (open the editor there first, THEN step) instead of blindly stepping human
|
|
272
|
+
# and moving on. Editing OFF: unchanged from before this function existed —
|
|
273
|
+
# step silently, refusal surfaced and halts, a clean/committed gate just falls
|
|
274
|
+
# into the loop below.
|
|
157
275
|
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
158
276
|
echo "gtd-loop: could not determine the next step:" >&2
|
|
159
277
|
echo "$peek_json" >&2
|
|
@@ -161,11 +279,15 @@ if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
|
161
279
|
exit 1
|
|
162
280
|
fi
|
|
163
281
|
if [[ "$(jq -r .kind <<<"$peek_json")" == "message" ]]; then
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
282
|
+
if [[ "$edit_disabled" == 1 ]]; then
|
|
283
|
+
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
284
|
+
echo "gtd-loop: could not capture your changes at the human gate:" >&2
|
|
285
|
+
echo "$step_out" >&2
|
|
286
|
+
echo "Fix the reported issue and re-run gtd-loop." >&2
|
|
287
|
+
exit 1
|
|
288
|
+
fi
|
|
289
|
+
else
|
|
290
|
+
handle_human_gate "$peek_json"
|
|
169
291
|
fi
|
|
170
292
|
fi
|
|
171
293
|
|
|
@@ -186,18 +308,15 @@ while true; do
|
|
|
186
308
|
label="$(jq -r '.label // .state' <<<"$next_json")"
|
|
187
309
|
GTD_LAST_LABEL="$label"
|
|
188
310
|
|
|
189
|
-
if [[ "$kind" != "message" ]]; then
|
|
190
|
-
herdr_report working "$label"
|
|
191
|
-
fi
|
|
192
|
-
|
|
193
311
|
if [[ "$kind" == "message" ]]; then
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
exit 0
|
|
312
|
+
# handle_human_gate exits 0/non-zero itself on a halt/refusal; it only
|
|
313
|
+
# returns here when a new commit was captured, so we keep driving.
|
|
314
|
+
handle_human_gate "$next_json"
|
|
315
|
+
continue
|
|
199
316
|
fi
|
|
200
317
|
|
|
318
|
+
herdr_report working "$label"
|
|
319
|
+
|
|
201
320
|
if [[ "$kind" == "script" ]]; then
|
|
202
321
|
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
203
322
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189288,7 +189288,11 @@ const STATUSES = /* @__PURE__ */ new Set([
|
|
|
189288
189288
|
* glob, is tolerated (trimmed); the glob itself is taken verbatim after that
|
|
189289
189289
|
* (so a glob containing further spaces, e.g. a path with a literal space in
|
|
189290
189290
|
* it, is preserved intact — only the FIRST space is the status/glob
|
|
189291
|
-
* separator).
|
|
189291
|
+
* separator). Operates on the ALREADY-Eta-RENDERED pattern string — a
|
|
189292
|
+
* workflow author may write `"A <%= it.vars.feedbackFile %>"` in `on:`, but
|
|
189293
|
+
* by the time it reaches this parser (or `matchesPattern` below) the edge
|
|
189294
|
+
* (`src/Edge.ts`'s `renderOnEdges`) has already substituted `it.vars`; this
|
|
189295
|
+
* module never renders anything itself.
|
|
189292
189296
|
*/
|
|
189293
189297
|
const parsePattern = (raw) => {
|
|
189294
189298
|
const trimmed = raw.trim();
|
|
@@ -190061,9 +190065,9 @@ const collectGroups = (raw) => {
|
|
|
190061
190065
|
* <name>:
|
|
190062
190066
|
* actor: <string> # forbidden on a commit state, required otherwise
|
|
190063
190067
|
* script: <string> # exactly one of script/prompt/message/commit
|
|
190064
|
-
* on: # a mapping, DECLARATION ORDER PRESERVED
|
|
190068
|
+
* on: # a mapping, DECLARATION ORDER PRESERVED — each <pattern> KEY is itself an Eta template, rendered against `it.vars` at the edge (src/Edge.ts's renderOnEdges) before the pure engine ever sees it
|
|
190065
190069
|
* "<pattern>": <targetState> # short form
|
|
190066
|
-
* "<pattern>": { to: <targetState>, describe: <sentence> } # with a human-readable route description
|
|
190070
|
+
* "<pattern>": { to: <targetState>, describe: <sentence> } # with a human-readable route description (describe is NEVER Eta-rendered, unlike the pattern key)
|
|
190067
190071
|
* initial: true # exactly one state across the whole workflow
|
|
190068
190072
|
* retry:
|
|
190069
190073
|
* max: <number>
|
|
@@ -190590,7 +190594,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true) =
|
|
|
190590
190594
|
};
|
|
190591
190595
|
//#endregion
|
|
190592
190596
|
//#region src/workflows/unified.yaml
|
|
190593
|
-
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 STATES.md §10 for 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 inline 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 SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts\n# and STATES.md). Expansion is a compile-time raw → raw pre-pass with NO runtime\n# footprint: the pure engine only ever sees the flat concrete states, and each\n# invocation's `as:` rename map reproduces the exact state names the flat\n# template used — so the compiled `WorkflowDefinition` is BYTE-IDENTICAL to the\n# frozen `unified.flat.yaml` (pinned by src/workflows/golden.test.ts). The\n# sub-machines split into DEDUP (a gate/loop authored once, invoked several\n# times) and ENCAPSULATION (a single-instance cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (adv-grilling) and technical (architecting)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\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 `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` 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# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects 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 -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# KNOWN LIMITATION: `on` pattern keys are NOT Eta templates — they keep the\n# LITERAL `.gtd/...` paths matching these vars' DEFAULT values. Repointing a\n# file var changes what templates read/write WITHOUT changing what the `on`\n# patterns match, desyncing the machine (see docs/configuration.md).\n\nvars:\n testCommand: npm test\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 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_VAR_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/architecting/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records .gtd/FEEDBACK.md, a green run cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (adv-checking/adv-fixing/adv-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe, checkLabel, fixLabel, escalateLabel]\n states:\n check:\n actor: check\n label: $checkLabel\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 .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fix\n \"M .gtd/FEEDBACK.md\": fix\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n fix:\n actor: agent\n label: $fixLabel\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\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 on:\n \"* **\": check\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n describe: $escalateDescribe\n\n # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> .gtd/FEEDBACK.md, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, checkScript, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n script: $checkScript\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/FEEDBACK.md\": $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 describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (adv-grilling) and\n # technical (architecting) phases of the advanced flow.\n qaLoop:\n params:\n [\n authorFile,\n authorPrompt,\n answerMessage,\n onAdvance,\n advanceDescribe,\n editDescribe,\n authorLabel,\n answerLabel,\n ]\n states:\n author:\n actor: agent\n label: $authorLabel\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n label: $answerLabel\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n describe: $advanceDescribe\n \"* **\":\n to: author\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\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. 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 step agent` yourself; the harness does\n that.\n on:\n \"* **\": plan-review\n\n plan-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n describe: >-\n Change nothing to accept the plan as-is and move on to implementation\n (**building**).\n \"* **\":\n to: planning\n describe: >-\n Edit the plan — rewrite a step, add a constraint, or leave an inline\n comment — to send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-start-check` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the diff below in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.startCommit %> -->`\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 The diff below is `it.reviewDiff` — on a feedback round it covers ONLY the\n changes made since the previous review, so you review just what changed,\n not items already signed off. Leave `<%= it.vars.reviewFile %>`\n uncommitted and finish your turn.\n <% if (it.reviewDiff.trim()) { %>\n\n ## Full diff under review\n\n ```diff\n <%~ it.reviewDiff %>\n ```\n <% } %>\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 cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on 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 `review-deciding`, which decides sign-off\n # vs. 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 sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — 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 step human`:\n\n - Tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to sign off and collapse the\n cycle into one commit (**squashing**).\n - Leave a comment to request changes — 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** → **feedback-building**). 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 \"* **\": review-deciding\n\n review-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 cycle 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 -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any non-.gtd/ file this round. Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT 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 mkdir -p .gtd\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 reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n # Which non-.gtd/ files did the human hand-edit this round?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -v '^\\.gtd/')\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 notes=\"\"\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 if [ \"$before\" != \"$after\" ]; then notes=1; fi\n if [ -n \"$codeFiles\" ] || [ -n \"$notes\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the diff and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions.\"\n if [ -n \"$notes\" ]; then\n echo\n echo \"## Notes the human added to REVIEW.md this round\"\n echo\n echo '```diff'\n git show HEAD --format= -- \"$reviewFile\"\n echo '```'\n fi\n if [ -n \"$codeFiles\" ]; then\n echo\n echo \"## Files the human hand-edited this round (already committed)\"\n echo\n echo '```diff'\n git show HEAD --format= -- $codeFiles\n echo '```'\n fi\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A .gtd/REVIEW_RAW.md\": feedback-collecting\n \"M .gtd/REVIEW_RAW.md\": feedback-collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n\n feedback-collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewRawFile %>` (delete it when done) and\n `<%= it.vars.reviewFeedbackFile %>` (write it). Do NOT change any other\n file — you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\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>`. Read the two RAW sections and 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 .gtd/REVIEW_FEEDBACK.md\": feedback-building\n \"M .gtd/REVIEW_FEEDBACK.md\": feedback-building\n\n feedback-building:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\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 edge\n # gate (`enforceFeedbackProgressGate` in src/program.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. `.gtd/`\n holds this workflow's own state — never create, edit, or delete anything\n under `.gtd/` except `<%= it.vars.reviewFeedbackFile %>`, which this\n prompt tells you to address and then delete.\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 \"* **\": checking\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\n spec-review:\n actor: agent\n label: Reviewing the package\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (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 # `fixing` cap has (see STATES.md / docs/configuration.md).\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. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\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 <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"M .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n spec-fix:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to address\n and then delete.\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 \"* **\": adv-checking\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\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 next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": adv-building\n \"C\": $onDrained\n\n adv-building:\n actor: agent\n label: Building\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/`; in\n particular do NOT delete the package file (the `spec-review` gate reads it\n after you) and never touch `<%= it.vars.nextFile %>` — the `picking` state\n owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\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 \"* **\": adv-checking\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, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, 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 %>\n on:\n \"* **\": picking\n\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n # Advanced package health triple: adv-checking/adv-fixing/adv-escalate -> spec-review.\n - submachine: makeGreen\n name: pkg\n as: { check: adv-checking, fix: adv-fixing, escalate: adv-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: start\n as: { check: start-check, blocked: start-blocked }\n with:\n onGreen: planning\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: adv-start\n as: { check: adv-start-check, blocked: adv-start-blocked }\n with:\n onGreen: adv-grilling\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review-start\n as: { check: review-start-check, blocked: review-start-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Advanced flow: product Q&A loop (adv-grilling/adv-grilling-answer -> architecting).\n - submachine: qaLoop\n name: grill\n as: { author: adv-grilling, answer: adv-grilling-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: architecting\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\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 answerMessage: |\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n product plan and move on to technical architecting (**architecting**).\n editDescribe: >-\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 (**adv-grilling**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the product plan\n answerLabel: Awaiting your answers\n\n # Advanced flow: technical Q&A loop (architecting/architecting-answer -> decompose).\n - submachine: qaLoop\n name: arch\n as: { author: architecting, answer: architecting-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\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 answerMessage: |\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n technical plan and move on to decomposing the work into packages\n (**decompose**).\n editDescribe: >-\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 (**architecting**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the technical plan\n answerLabel: Awaiting your answers\n\n # Simple flow plan-iteration loop (planning/plan-review -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/adv-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\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 cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `.gtd/TODO.md` — a short sketch — starts the SIMPLE flow (plan, build,\n review).\n - `.gtd/REQUIREMENTS.md` — the product requirements — starts the ADVANCED\n flow (product + technical Q&A, package decomposition, parallel build,\n per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* .gtd/REQUIREMENTS.md\":\n to: adv-start-check\n describe: >-\n Save the product requirements to `.gtd/REQUIREMENTS.md` to start the\n ADVANCED flow — gtd first checks the test baseline is green, then the\n agent develops them through product then technical Q&A, decomposes the\n work into packages, and builds each in parallel (**adv-start-check**).\n \"* **\":\n to: start-check\n describe: >-\n Save a short sketch to `.gtd/TODO.md` (or any other change) to start\n the SIMPLE flow — gtd first checks the test baseline is green, then the\n agent reads it, explores the codebase, and develops it into a concrete\n implementation plan (**start-check**).\n\n # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once its\n work is complete.\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 \"* **\": checking\n\n # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\n decompose:\n actor: agent\n label: Decomposing into packages\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except the\n package files this prompt tells you to write (under\n `<%= it.vars.packagesDir %>/`), and `<%= it.vars.architectureFile %>`,\n which it tells you to delete.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered 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`, ...). Order the packages so the\n test suite can pass again after each one lands.\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 \"* .gtd/packages/**\": picking\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-check:\n actor: check\n fixEntry: true\n label: Checking the baseline\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite to find what needs fixing; a red run\n # records .gtd/FEEDBACK.md (-> fixing), a green run cleans it up (-> idle).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: squash finale (review loop is the `humanReview` sub-machine) ─\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done.\n `.gtd/` holds this workflow's own state — never create, edit, or delete\n anything under `.gtd/` except `<%= it.vars.commitMsgFile %>`, which this\n prompt tells you to write.\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. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A .gtd/COMMIT_MSG.md\": done\n \"M .gtd/COMMIT_MSG.md\": done\n\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";
|
|
190597
|
+
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 STATES.md §10 for 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 inline 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 SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts\n# and STATES.md). Expansion is a compile-time raw → raw pre-pass with NO runtime\n# footprint: the pure engine only ever sees the flat concrete states, and each\n# invocation's `as:` rename map reproduces the exact state names the flat\n# template used — so the compiled `WorkflowDefinition` is BYTE-IDENTICAL to the\n# frozen `unified.flat.yaml` (pinned by src/workflows/golden.test.ts). The\n# sub-machines split into DEDUP (a gate/loop authored once, invoked several\n# times) and ENCAPSULATION (a single-instance cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (adv-grilling) and technical (architecting)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\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 `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` 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# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects 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 -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `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 docs/configuration.md).\n\nvars:\n testCommand: npm test\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 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_VAR_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/architecting/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records .gtd/FEEDBACK.md, a green run cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (adv-checking/adv-fixing/adv-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe, checkLabel, fixLabel, escalateLabel]\n states:\n check:\n actor: check\n label: $checkLabel\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 .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": fix\n \"M <%= it.vars.feedbackFile %>\": fix\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n fix:\n actor: agent\n label: $fixLabel\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\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 on:\n \"* **\": check\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n describe: $escalateDescribe\n\n # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> .gtd/FEEDBACK.md, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, checkScript, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n script: $checkScript\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 describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (adv-grilling) and\n # technical (architecting) phases of the advanced flow.\n qaLoop:\n params:\n [\n authorFile,\n authorPrompt,\n answerMessage,\n onAdvance,\n advanceDescribe,\n editDescribe,\n authorLabel,\n answerLabel,\n ]\n states:\n author:\n actor: agent\n label: $authorLabel\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n label: $answerLabel\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n describe: $advanceDescribe\n \"* **\":\n to: author\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\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. 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 step agent` yourself; the harness does\n that.\n on:\n \"* **\": plan-review\n\n plan-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n describe: >-\n Change nothing to accept the plan as-is and move on to implementation\n (**building**).\n \"* **\":\n to: planning\n describe: >-\n Edit the plan — rewrite a step, add a constraint, or leave an inline\n comment — to send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-start-check` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the diff below in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.startCommit %> -->`\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 The diff below is `it.reviewDiff` — on a feedback round it covers ONLY the\n changes made since the previous review, so you review just what changed,\n not items already signed off. Leave `<%= it.vars.reviewFile %>`\n uncommitted and finish your turn.\n <% if (it.reviewDiff.trim()) { %>\n\n ## Full diff under review\n\n ```diff\n <%~ it.reviewDiff %>\n ```\n <% } %>\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 cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on 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 `review-deciding`, which decides sign-off\n # vs. 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 sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — 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 step human`:\n\n - Tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to sign off and collapse the\n cycle into one commit (**squashing**).\n - Leave a comment to request changes — 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** → **feedback-building**). 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 \"* **\": review-deciding\n\n review-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 cycle 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 -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any non-.gtd/ file this round. Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT 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 mkdir -p .gtd\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 reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n # Which non-.gtd/ files did the human hand-edit this round?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -v '^\\.gtd/')\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 notes=\"\"\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 if [ \"$before\" != \"$after\" ]; then notes=1; fi\n if [ -n \"$codeFiles\" ] || [ -n \"$notes\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the diff and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions.\"\n if [ -n \"$notes\" ]; then\n echo\n echo \"## Notes the human added to REVIEW.md this round\"\n echo\n echo '```diff'\n git show HEAD --format= -- \"$reviewFile\"\n echo '```'\n fi\n if [ -n \"$codeFiles\" ]; then\n echo\n echo \"## Files the human hand-edited this round (already committed)\"\n echo\n echo '```diff'\n git show HEAD --format= -- $codeFiles\n echo '```'\n fi\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"M <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n feedback-collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewRawFile %>` (delete it when done) and\n `<%= it.vars.reviewFeedbackFile %>` (write it). Do NOT change any other\n file — you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\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>`. Read the two RAW sections and 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 %>\": feedback-building\n \"M <%= it.vars.reviewFeedbackFile %>\": feedback-building\n\n feedback-building:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\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 edge\n # gate (`enforceFeedbackProgressGate` in src/program.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. `.gtd/`\n holds this workflow's own state — never create, edit, or delete anything\n under `.gtd/` except `<%= it.vars.reviewFeedbackFile %>`, which this\n prompt tells you to address and then delete.\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 \"* **\": checking\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\n spec-review:\n actor: agent\n label: Reviewing the package\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (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 # `fixing` cap has (see STATES.md / docs/configuration.md).\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. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\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 <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A <%= it.vars.specFeedbackFile %>\": spec-fix\n \"M <%= it.vars.specFeedbackFile %>\": spec-fix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n spec-fix:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to address\n and then delete.\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 \"* **\": adv-checking\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\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 next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\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 %>\": adv-building\n \"C\": $onDrained\n\n adv-building:\n actor: agent\n label: Building\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/`; in\n particular do NOT delete the package file (the `spec-review` gate reads it\n after you) and never touch `<%= it.vars.nextFile %>` — the `picking` state\n owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\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 \"* **\": adv-checking\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, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, 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 %>\n on:\n \"* **\": picking\n\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n # Advanced package health triple: adv-checking/adv-fixing/adv-escalate -> spec-review.\n - submachine: makeGreen\n name: pkg\n as: { check: adv-checking, fix: adv-fixing, escalate: adv-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: start\n as: { check: start-check, blocked: start-blocked }\n with:\n onGreen: planning\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: adv-start\n as: { check: adv-start-check, blocked: adv-start-blocked }\n with:\n onGreen: adv-grilling\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review-start\n as: { check: review-start-check, blocked: review-start-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Advanced flow: product Q&A loop (adv-grilling/adv-grilling-answer -> architecting).\n - submachine: qaLoop\n name: grill\n as: { author: adv-grilling, answer: adv-grilling-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: architecting\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\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 answerMessage: |\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n product plan and move on to technical architecting (**architecting**).\n editDescribe: >-\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 (**adv-grilling**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the product plan\n answerLabel: Awaiting your answers\n\n # Advanced flow: technical Q&A loop (architecting/architecting-answer -> decompose).\n - submachine: qaLoop\n name: arch\n as: { author: architecting, answer: architecting-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\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 answerMessage: |\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 step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n technical plan and move on to decomposing the work into packages\n (**decompose**).\n editDescribe: >-\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 (**architecting**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the technical plan\n answerLabel: Awaiting your answers\n\n # Simple flow plan-iteration loop (planning/plan-review -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/adv-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\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 cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `.gtd/TODO.md` — a short sketch — starts the SIMPLE flow (plan, build,\n review).\n - `.gtd/REQUIREMENTS.md` — the product requirements — starts the ADVANCED\n flow (product + technical Q&A, package decomposition, parallel build,\n per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* <%= it.vars.requirementsFile %>\":\n to: adv-start-check\n describe: >-\n Save the product requirements to `.gtd/REQUIREMENTS.md` to start the\n ADVANCED flow — gtd first checks the test baseline is green, then the\n agent develops them through product then technical Q&A, decomposes the\n work into packages, and builds each in parallel (**adv-start-check**).\n \"* **\":\n to: start-check\n describe: >-\n Save a short sketch to `.gtd/TODO.md` (or any other change) to start\n the SIMPLE flow — gtd first checks the test baseline is green, then the\n agent reads it, explores the codebase, and develops it into a concrete\n implementation plan (**start-check**).\n\n # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once its\n work is complete.\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 \"* **\": checking\n\n # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\n decompose:\n actor: agent\n label: Decomposing into packages\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except the\n package files this prompt tells you to write (under\n `<%= it.vars.packagesDir %>/`), and `<%= it.vars.architectureFile %>`,\n which it tells you to delete.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered 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`, ...). Order the packages so the\n test suite can pass again after each one lands.\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 %>/**\": picking\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-check:\n actor: check\n fixEntry: true\n label: Checking the baseline\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite to find what needs fixing; a red run\n # records .gtd/FEEDBACK.md (-> fixing), a green run cleans it up (-> idle).\n set +e\n mkdir -p .gtd\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 <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": fixing\n \"M <%= it.vars.feedbackFile %>\": fixing\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: squash finale (review loop is the `humanReview` sub-machine) ─\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done.\n `.gtd/` holds this workflow's own state — never create, edit, or delete\n anything under `.gtd/` except `<%= it.vars.commitMsgFile %>`, which this\n prompt tells you to write.\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. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A <%= it.vars.commitMsgFile %>\": done\n \"M <%= it.vars.commitMsgFile %>\": done\n\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";
|
|
190594
190598
|
//#endregion
|
|
190595
190599
|
//#region src/workflows/templates.ts
|
|
190596
190600
|
/**
|
|
@@ -192612,6 +192616,33 @@ var Eta = class extends Eta$1 {
|
|
|
192612
192616
|
};
|
|
192613
192617
|
//#endregion
|
|
192614
192618
|
//#region src/PatternTemplates.ts
|
|
192619
|
+
/**
|
|
192620
|
+
* The stub `TemplateContext` an `on` pattern key renders against at the edge
|
|
192621
|
+
* (see `Edge.ts`'s `renderOnEdges`): only `vars` (and, optionally, `state`)
|
|
192622
|
+
* are real — every commit-ish/diff field is empty, `processCost` is `0`,
|
|
192623
|
+
* `edges` is empty, and `read` throws, since a pattern names a path and never
|
|
192624
|
+
* legitimately needs a working tree or git history to resolve. This is also
|
|
192625
|
+
* `Lsp.ts`'s `buildFileModeMap` map-building context, factored out here to
|
|
192626
|
+
* avoid building the same stub in two places.
|
|
192627
|
+
*/
|
|
192628
|
+
const varsOnlyContext = (vars, state = "") => ({
|
|
192629
|
+
startCommit: "",
|
|
192630
|
+
currentCommit: "",
|
|
192631
|
+
previousCommit: "",
|
|
192632
|
+
state,
|
|
192633
|
+
actor: "",
|
|
192634
|
+
processDiff: "",
|
|
192635
|
+
reviewDiff: "",
|
|
192636
|
+
retainedDiff: "",
|
|
192637
|
+
lastDiff: "",
|
|
192638
|
+
processCost: 0,
|
|
192639
|
+
processCostByModel: [],
|
|
192640
|
+
read: (path) => {
|
|
192641
|
+
throw new Error(`no working tree to read from while rendering against a vars-only context (path: ${path})`);
|
|
192642
|
+
},
|
|
192643
|
+
vars,
|
|
192644
|
+
edges: []
|
|
192645
|
+
});
|
|
192615
192646
|
const eta = new Eta();
|
|
192616
192647
|
eta.readFile = null;
|
|
192617
192648
|
eta.resolvePath = null;
|
|
@@ -192869,7 +192900,7 @@ const resolveVars = (workflowVars, rcVars, env) => ({
|
|
|
192869
192900
|
...rcVars,
|
|
192870
192901
|
...envVarsFrom(env)
|
|
192871
192902
|
});
|
|
192872
|
-
/** Map a state's raw `on` edges to the `{ pattern, target, describe? }` shape templates see as `it.edges`. `undefined` (a commit state, no `on`) yields an empty list. */
|
|
192903
|
+
/** Map a state's raw `on` edges to the `{ pattern, target, describe? }` shape templates see as `it.edges`. `undefined` (a commit state, no `on`) yields an empty list. Callers pass already-rendered edges (see `renderOnEdges`) — this never renders anything itself. */
|
|
192873
192904
|
const toTemplateEdges = (edges) => (edges ?? []).map(([pattern, target, describe]) => describe !== void 0 ? {
|
|
192874
192905
|
pattern,
|
|
192875
192906
|
target,
|
|
@@ -192879,10 +192910,48 @@ const toTemplateEdges = (edges) => (edges ?? []).map(([pattern, target, describe
|
|
|
192879
192910
|
target
|
|
192880
192911
|
});
|
|
192881
192912
|
/**
|
|
192913
|
+
* Render every `on` edge's pattern key as an Eta template over `vars` ONLY
|
|
192914
|
+
* (`PatternTemplates.varsOnlyContext`) — a pattern names a path; it never
|
|
192915
|
+
* needs diffs/commit hashes, and restricting to `vars` avoids an ordering
|
|
192916
|
+
* circularity (the full `TemplateContext`'s `it.edges` is itself derived from
|
|
192917
|
+
* these same `on` edges). `target`/`describe` pass through verbatim —
|
|
192918
|
+
* `describe` is inert to the engine and is never rendered, unlike the pattern
|
|
192919
|
+
* key. Throws whatever Eta throws on a malformed pattern template; the caller
|
|
192920
|
+
* turns that into a step refusal / command error, exactly like a content
|
|
192921
|
+
* render failure.
|
|
192922
|
+
*/
|
|
192923
|
+
const renderOnEdges = (edges, vars) => {
|
|
192924
|
+
const ctx = varsOnlyContext(vars);
|
|
192925
|
+
return (edges ?? []).map(([pattern, target, describe]) => describe !== void 0 ? [
|
|
192926
|
+
renderStateTemplate(pattern, ctx),
|
|
192927
|
+
target,
|
|
192928
|
+
describe
|
|
192929
|
+
] : [renderStateTemplate(pattern, ctx), target]);
|
|
192930
|
+
};
|
|
192931
|
+
/**
|
|
192932
|
+
* A shallow clone of `def` whose `state`'s `on` is replaced by
|
|
192933
|
+
* `renderedOnEdges` — used to feed `PatternMachine.step`, which matches only
|
|
192934
|
+
* `def.states[state].on` for the state it's invoked at. Only the RESTING
|
|
192935
|
+
* state needs patching: `step`'s retry/target logic keys on state NAMES, not
|
|
192936
|
+
* on any other state's `on`, so every other state is left as-is.
|
|
192937
|
+
*/
|
|
192938
|
+
const withRenderedOn = (def, state, renderedOnEdges) => ({
|
|
192939
|
+
...def,
|
|
192940
|
+
states: {
|
|
192941
|
+
...def.states,
|
|
192942
|
+
[state]: {
|
|
192943
|
+
...def.states[state],
|
|
192944
|
+
on: renderedOnEdges
|
|
192945
|
+
}
|
|
192946
|
+
}
|
|
192947
|
+
});
|
|
192948
|
+
/**
|
|
192882
192949
|
* Build the `PatternTemplates.TemplateContext` for rendering `state`'s content
|
|
192883
192950
|
* at the resolved rest. `vars` is the already-merged three-layer map (see
|
|
192884
|
-
* `resolveVars`); `edges` is the resting state's own `on` edges
|
|
192885
|
-
* `
|
|
192951
|
+
* `resolveVars`); `edges` is the resting state's own `on` edges, ALREADY
|
|
192952
|
+
* RENDERED by the caller (`renderOnEdges`) — this function never renders a
|
|
192953
|
+
* pattern itself, it only maps the given edges into `it.edges`
|
|
192954
|
+
* (`toTemplateEdges`). `currentCost`/`currentModel` are the in-flight step's own
|
|
192886
192955
|
* `--cost`/`--model` (folded into the process's committed cost entries so a
|
|
192887
192956
|
* `commit:` squash template sees the whole-process total AND per-model
|
|
192888
192957
|
* breakdown including the squashing step) — `0`/absent for the pure emitters
|
|
@@ -193002,7 +193071,7 @@ const renderRest = (rest, context) => gen(function* () {
|
|
|
193002
193071
|
kind,
|
|
193003
193072
|
content,
|
|
193004
193073
|
...omitUndefined(hints),
|
|
193005
|
-
edges:
|
|
193074
|
+
edges: context.edges
|
|
193006
193075
|
};
|
|
193007
193076
|
});
|
|
193008
193077
|
/**
|
|
@@ -204837,24 +204906,7 @@ const buildFileModeMap = (def, vars, root) => {
|
|
|
204837
204906
|
if (stateDef.file === void 0 || stateDef.mode === void 0) continue;
|
|
204838
204907
|
let rendered;
|
|
204839
204908
|
try {
|
|
204840
|
-
rendered = renderStateTemplate(stateDef.file,
|
|
204841
|
-
startCommit: "",
|
|
204842
|
-
currentCommit: "",
|
|
204843
|
-
previousCommit: "",
|
|
204844
|
-
state: name,
|
|
204845
|
-
actor: "",
|
|
204846
|
-
processDiff: "",
|
|
204847
|
-
reviewDiff: "",
|
|
204848
|
-
retainedDiff: "",
|
|
204849
|
-
lastDiff: "",
|
|
204850
|
-
processCost: 0,
|
|
204851
|
-
processCostByModel: [],
|
|
204852
|
-
read: (path) => {
|
|
204853
|
-
throw new Error(`"file:" is not readable while building the path→mode map (path: ${path})`);
|
|
204854
|
-
},
|
|
204855
|
-
vars,
|
|
204856
|
-
edges: []
|
|
204857
|
-
});
|
|
204909
|
+
rendered = renderStateTemplate(stateDef.file, varsOnlyContext(vars, name));
|
|
204858
204910
|
} catch (e) {
|
|
204859
204911
|
warnings.push(`state "${name}": "file:" failed to render, skipped — ${e instanceof Error ? e.message : String(e)}`);
|
|
204860
204912
|
continue;
|
|
@@ -205076,8 +205128,8 @@ const stripUndefined = (o) => {
|
|
|
205076
205128
|
for (const key of Object.keys(o)) if (o[key] === void 0) delete o[key];
|
|
205077
205129
|
return o;
|
|
205078
205130
|
};
|
|
205079
|
-
/** Describe one compiled state for the viewer (optional fields omitted when unset). */
|
|
205080
|
-
const toVizState = (name, def, group, incoming) => stripUndefined({
|
|
205131
|
+
/** Describe one compiled state for the viewer (optional fields omitted when unset). `onEdges` is that state's `on`, ALREADY RENDERED against `it.vars` (see `buildVizModel`) — never `def.on` directly, which would show the unrendered literal. */
|
|
205132
|
+
const toVizState = (name, def, onEdges, group, incoming) => stripUndefined({
|
|
205081
205133
|
name,
|
|
205082
205134
|
actor: def.actor,
|
|
205083
205135
|
kind: contentKindOf(def) ?? "unknown",
|
|
@@ -205089,15 +205141,31 @@ const toVizState = (name, def, group, incoming) => stripUndefined({
|
|
|
205089
205141
|
mode: def.mode,
|
|
205090
205142
|
retry: def.retry,
|
|
205091
205143
|
flags: flagsOf(def),
|
|
205092
|
-
on:
|
|
205144
|
+
on: onEdges.map(edgeToViz),
|
|
205093
205145
|
incoming,
|
|
205094
205146
|
group
|
|
205095
205147
|
});
|
|
205148
|
+
/** Render one `on` pattern key against `vars` (see `Edge.ts`'s `renderOnEdges`), falling back to the raw pattern string on a render failure — the viewer is best-effort, unlike a real step (which refuses). */
|
|
205149
|
+
const renderPatternOrRaw = (pattern, vars) => {
|
|
205150
|
+
try {
|
|
205151
|
+
return renderStateTemplate(pattern, varsOnlyContext(vars));
|
|
205152
|
+
} catch {
|
|
205153
|
+
return pattern;
|
|
205154
|
+
}
|
|
205155
|
+
};
|
|
205156
|
+
/** Render every `on` edge of every state against `vars` (pattern key only — `target`/`describe` pass through verbatim), keyed by state name. */
|
|
205157
|
+
const renderedOnByState = (workflow, vars) => new Map(Object.entries(workflow.states).map(([name, def]) => [name, (def.on ?? []).map(([pattern, target, describe]) => describe !== void 0 ? [
|
|
205158
|
+
renderPatternOrRaw(pattern, vars),
|
|
205159
|
+
target,
|
|
205160
|
+
describe
|
|
205161
|
+
] : [renderPatternOrRaw(pattern, vars), target])]));
|
|
205096
205162
|
/**
|
|
205097
205163
|
* Build the viewer's JSON description from the active COMPILED workflow plus its
|
|
205098
205164
|
* RAW value (the pre-expansion `submachines:`/`use:` form — `rawWorkflow` from
|
|
205099
205165
|
* `ConfigService`), which is the only place the sub-machine grouping survives
|
|
205100
|
-
* (`compileWorkflowConfig` flattens it away). `vars` is shown for reference
|
|
205166
|
+
* (`compileWorkflowConfig` flattens it away). `vars` is shown for reference, AND
|
|
205167
|
+
* used to render every state's `on` pattern (see `renderedOnByState`) so the
|
|
205168
|
+
* diagram shows real paths rather than a repointed var's stale literal.
|
|
205101
205169
|
*/
|
|
205102
205170
|
const buildVizModel = (workflow, rawWorkflow, vars) => {
|
|
205103
205171
|
const groups = collectGroups(rawWorkflow);
|
|
@@ -205107,6 +205175,7 @@ const buildVizModel = (workflow, rawWorkflow, vars) => {
|
|
|
205107
205175
|
const cur = groupOf.get(s);
|
|
205108
205176
|
if (cur === void 0 || sizeOf.get(g.name) < sizeOf.get(cur)) groupOf.set(s, g.name);
|
|
205109
205177
|
}
|
|
205178
|
+
const renderedOn = renderedOnByState(workflow, vars);
|
|
205110
205179
|
const incoming = /* @__PURE__ */ new Map();
|
|
205111
205180
|
const addIncoming = (target, from, pattern) => {
|
|
205112
205181
|
const list = incoming.get(target) ?? [];
|
|
@@ -205117,11 +205186,11 @@ const buildVizModel = (workflow, rawWorkflow, vars) => {
|
|
|
205117
205186
|
incoming.set(target, list);
|
|
205118
205187
|
};
|
|
205119
205188
|
for (const [name, def] of Object.entries(workflow.states)) {
|
|
205120
|
-
for (const [pattern, to] of
|
|
205189
|
+
for (const [pattern, to] of renderedOn.get(name) ?? []) addIncoming(to, name, pattern);
|
|
205121
205190
|
if (def.retry) addIncoming(def.retry.otherwise, name, `retry ×${def.retry.max}`);
|
|
205122
205191
|
}
|
|
205123
205192
|
return {
|
|
205124
|
-
states: Object.entries(workflow.states).map(([name, def]) => toVizState(name, def, groupOf.get(name), incoming.get(name) ?? [])),
|
|
205193
|
+
states: Object.entries(workflow.states).map(([name, def]) => toVizState(name, def, renderedOn.get(name) ?? [], groupOf.get(name), incoming.get(name) ?? [])),
|
|
205125
205194
|
initial: initialStateOf(workflow),
|
|
205126
205195
|
groups: groups.map((g) => ({
|
|
205127
205196
|
name: g.name,
|
|
@@ -205135,12 +205204,15 @@ const buildVizModel = (workflow, rawWorkflow, vars) => {
|
|
|
205135
205204
|
* Describe the currently-rested state for the viewer: its `on` edges each
|
|
205136
205205
|
* flagged with whether it's the one that would fire on the CURRENT pending
|
|
205137
205206
|
* changes (same first-match semantics `PatternMachine.step` decides a real
|
|
205138
|
-
* step with), plus the retry redirect and pending changes verbatim. `
|
|
205139
|
-
* is
|
|
205140
|
-
*
|
|
205141
|
-
|
|
205142
|
-
|
|
205143
|
-
|
|
205207
|
+
* step with), plus the retry redirect and pending changes verbatim. `onEdges`
|
|
205208
|
+
* is the resting state's `on` edges ALREADY RENDERED against `it.vars` (see
|
|
205209
|
+
* `Edge.ts`'s `renderOnEdges`) — this function never renders anything itself,
|
|
205210
|
+
* it only matches/emits from what it's given (never `rest.stateDef.on`, which
|
|
205211
|
+
* would be the unrendered literal). `group` is threaded in by the caller
|
|
205212
|
+
* (already computed once by `buildVizModel` from the same workflow) rather
|
|
205213
|
+
* than recomputed here.
|
|
205214
|
+
*/
|
|
205215
|
+
const buildCurrentStateModel = (rest, changes, onEdges, group) => {
|
|
205144
205216
|
const matchedIndex = onEdges.findIndex(([patternStr]) => {
|
|
205145
205217
|
const parsed = parsePattern(patternStr);
|
|
205146
205218
|
return parsed !== void 0 && matchesPattern(parsed, changes);
|
|
@@ -205356,6 +205428,10 @@ Commands:
|
|
|
205356
205428
|
an agent through gtd next/gtd step calls until the
|
|
205357
205429
|
workflow returns to its initial state again. A bare gtd
|
|
205358
205430
|
invocation and gtd loop both launch it identically
|
|
205431
|
+
edit [path] Open \${VISUAL:-$EDITOR} on <path>, or — with no path — on
|
|
205432
|
+
the resolved rest's declared file (or the repo directory
|
|
205433
|
+
if it declares none). Handled entirely by bin/gtd, not
|
|
205434
|
+
this bundle
|
|
205359
205435
|
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
205360
205436
|
default variables you are most likely to change (the test
|
|
205361
205437
|
command) and a Prettier formatting suggestion. gtd runs its
|
|
@@ -205538,13 +205614,20 @@ const runInitCommand = (argv, json, write) => gen(function* () {
|
|
|
205538
205614
|
}) + "\n");
|
|
205539
205615
|
else write("Wrote .gtdrc.json seeding the default variables (the test command) and a\nPrettier formatting suggestion. gtd runs its built-in workflow by default — add\na workflow: key only if you want to customize the machine itself.\n\n" + (inRepo ? "Review and commit it before starting: an uncommitted .gtdrc.json counts as a\npending change, so the initial state would capture it on the first step. Once\ncommitted, run `gtd step human` to begin.\n" : "This directory is not a git repository, so there is nothing to commit here. The\nconfig applies to any gtd repository nested below it — gtd discovers it by\nwalking up from the repository root. Run `gtd step human` from such a repo to\nbegin.\n"));
|
|
205540
205616
|
});
|
|
205617
|
+
/** Render a state's `on` edges against `vars` (see `renderOnEdges`), surfacing a malformed pattern template as a plain command error, exactly like a content render failure. */
|
|
205618
|
+
const renderOnEdgesOrFail = (onEdges, vars) => try_({
|
|
205619
|
+
try: () => renderOnEdges(onEdges, vars),
|
|
205620
|
+
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
205621
|
+
});
|
|
205541
205622
|
/**
|
|
205542
205623
|
* Resolve HEAD's rest, the current process run, and the template context for
|
|
205543
205624
|
* rendering that rest's OWN state/actor — the common prefix shared by `gtd
|
|
205544
205625
|
* next` and `gtd status` (each fetches `git` itself first, since
|
|
205545
205626
|
* `gtd status` also needs it for `pendingChanges`; `stepAsActor`'s squash
|
|
205546
205627
|
* path renders a DIFFERENT state, so it builds its own context inline
|
|
205547
|
-
* instead of sharing this helper).
|
|
205628
|
+
* instead of sharing this helper). `renderedOn` is the resting state's `on`
|
|
205629
|
+
* edges already rendered against `it.vars` (`renderOnEdges`) — `next`/`status`
|
|
205630
|
+
* reuse it instead of re-rendering the same patterns themselves.
|
|
205548
205631
|
*/
|
|
205549
205632
|
const resolveRestContext = (git) => gen(function* () {
|
|
205550
205633
|
const config = yield* (yield* ConfigService).load;
|
|
@@ -205553,11 +205636,13 @@ const resolveRestContext = (git) => gen(function* () {
|
|
|
205553
205636
|
const rest = yield* resolveRest();
|
|
205554
205637
|
const run = yield* computeProcessRun(git, rest.def);
|
|
205555
205638
|
const vars = resolveVars(config.workflowVars, config.rcVars, envVars.all);
|
|
205639
|
+
const renderedOn = yield* renderOnEdgesOrFail(rest.stateDef.on, vars);
|
|
205556
205640
|
const reviewBase = yield* reviewBaseHash(git, rest.def, run);
|
|
205557
205641
|
return {
|
|
205558
205642
|
rest,
|
|
205559
205643
|
run,
|
|
205560
|
-
context: yield* buildTemplateContext(git, worktree.read, rest.state, rest.actor, run, vars,
|
|
205644
|
+
context: yield* buildTemplateContext(git, worktree.read, rest.state, rest.actor, run, vars, renderedOn, 0, void 0, reviewBase),
|
|
205645
|
+
renderedOn
|
|
205561
205646
|
};
|
|
205562
205647
|
});
|
|
205563
205648
|
/** The user-facing message for a `step` refusal — out-of-turn names the awaited actor, no-match names every declared pattern. */
|
|
@@ -205577,7 +205662,9 @@ const stepAsActor = (invoker, cost, model) => gen(function* () {
|
|
|
205577
205662
|
const rest = yield* resolveRest();
|
|
205578
205663
|
const run = yield* computeProcessRun(git, rest.def);
|
|
205579
205664
|
const changes = yield* pendingChanges(git);
|
|
205580
|
-
const
|
|
205665
|
+
const vars = resolveVars(config.workflowVars, config.rcVars, envVars.all);
|
|
205666
|
+
const renderedOn = yield* renderOnEdgesOrFail(rest.stateDef.on, vars);
|
|
205667
|
+
const decision = step(withRenderedOn(rest.def, rest.state, renderedOn), rest.state, invoker, {
|
|
205581
205668
|
changes,
|
|
205582
205669
|
processTrace: run.trace
|
|
205583
205670
|
});
|
|
@@ -205589,9 +205676,9 @@ const stepAsActor = (invoker, cost, model) => gen(function* () {
|
|
|
205589
205676
|
model: null
|
|
205590
205677
|
};
|
|
205591
205678
|
const executable = decision;
|
|
205592
|
-
const vars = resolveVars(config.workflowVars, config.rcVars, envVars.all);
|
|
205593
205679
|
const renderedState = decision.kind === "squash" ? decision.state : rest.state;
|
|
205594
|
-
const
|
|
205680
|
+
const renderedStateOn = renderedState === rest.state ? renderedOn : yield* renderOnEdgesOrFail(rest.def.states[renderedState]?.on, vars);
|
|
205681
|
+
const context = yield* buildTemplateContext(git, worktree.read, renderedState, invoker, run, vars, renderedStateOn, cost ?? 0, model);
|
|
205595
205682
|
yield* enforceSteeringGate(worktree.read, invoker, rest, context, decision.kind);
|
|
205596
205683
|
yield* enforceReviewSignoffGate(worktree.read, invoker, rest, context, changes, decision.kind);
|
|
205597
205684
|
yield* enforceFeedbackProgressGate(invoker, rest, context, changes, decision.kind);
|
|
@@ -206064,7 +206151,7 @@ const runValidateCommand = (argv, json, write) => gen(function* () {
|
|
|
206064
206151
|
}
|
|
206065
206152
|
return yield* fail$2(/* @__PURE__ */ new Error(`${file} is not valid:\n${errors.map((e) => ` - ${e}`).join("\n")}`));
|
|
206066
206153
|
});
|
|
206067
|
-
/** Which declared `on` pattern (if any) each pending change matches — the pure computation `gtd status` reports (both plain and `--json`). */
|
|
206154
|
+
/** 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 step` would match against. */
|
|
206068
206155
|
const computeStatusChanges = (onEdges, changes) => changes.map((change) => {
|
|
206069
206156
|
const matchedRow = onEdges.find(([patternStr]) => {
|
|
206070
206157
|
const parsed = parsePattern(patternStr);
|
|
@@ -206089,9 +206176,9 @@ const costStatusLines = (cost, byModel) => {
|
|
|
206089
206176
|
if (breakdownIsInformative(byModel)) for (const m of byModel) lines.push(` ${m.model}: ${m.cost}`);
|
|
206090
206177
|
return lines;
|
|
206091
206178
|
};
|
|
206092
|
-
/** `gtd status --json`'s emission — `{state, actor, changes, model?, memory?, label?, file?, mode?, cost?, costByModel?, edges?}`. */
|
|
206093
|
-
const writeStatusJson = (write, rest, statusChanges, model, memory, label, file, cost, costByModel) => {
|
|
206094
|
-
const edges = toTemplateEdges(
|
|
206179
|
+
/** `gtd status --json`'s emission — `{state, actor, changes, model?, memory?, label?, file?, mode?, cost?, costByModel?, edges?}`. `renderedOn` is the resting state's `on` edges already rendered against `it.vars` (`renderOnEdges`), so the emitted `edges[].pattern` carries the same rendered path as `changes[].pattern`. */
|
|
206180
|
+
const writeStatusJson = (write, rest, statusChanges, renderedOn, model, memory, label, file, cost, costByModel) => {
|
|
206181
|
+
const edges = toTemplateEdges(renderedOn);
|
|
206095
206182
|
write(JSON.stringify({
|
|
206096
206183
|
state: rest.state,
|
|
206097
206184
|
actor: rest.actor,
|
|
@@ -206126,14 +206213,14 @@ const writeStatusPlain = (write, rest, statusChanges, model, memory, label, file
|
|
|
206126
206213
|
const runStatusCommand = (argv, json, write) => gen(function* () {
|
|
206127
206214
|
yield* rejectExtraArgs("status", argv);
|
|
206128
206215
|
const git = yield* GitService;
|
|
206129
|
-
const { rest, context } = yield* resolveRestContext(git);
|
|
206216
|
+
const { rest, context, renderedOn } = yield* resolveRestContext(git);
|
|
206130
206217
|
const changes = yield* pendingChanges(git);
|
|
206131
206218
|
const model = yield* renderModel(rest.stateDef, context);
|
|
206132
206219
|
const memory = yield* renderMemory(rest.stateDef, context);
|
|
206133
206220
|
const label = yield* renderLabel(rest.stateDef, context);
|
|
206134
206221
|
const file = yield* renderFile(rest.stateDef, context);
|
|
206135
|
-
const statusChanges = computeStatusChanges(
|
|
206136
|
-
if (json) writeStatusJson(write, rest, statusChanges, model, memory, label, file, context.processCost, context.processCostByModel);
|
|
206222
|
+
const statusChanges = computeStatusChanges(renderedOn, changes);
|
|
206223
|
+
if (json) writeStatusJson(write, rest, statusChanges, renderedOn, model, memory, label, file, context.processCost, context.processCostByModel);
|
|
206137
206224
|
else writeStatusPlain(write, rest, statusChanges, model, memory, label, file, context.processCost, context.processCostByModel);
|
|
206138
206225
|
});
|
|
206139
206226
|
/** Parse a `--port` value: an integer 0–65535, or `undefined` if invalid/absent. */
|
|
@@ -206194,11 +206281,15 @@ const parseVisualizeOptions = (rest) => {
|
|
|
206194
206281
|
*/
|
|
206195
206282
|
const computeCurrentState = (model) => gen(function* () {
|
|
206196
206283
|
const git = yield* GitService;
|
|
206284
|
+
const config = yield* (yield* ConfigService).load;
|
|
206285
|
+
const envVars = yield* EnvVars;
|
|
206197
206286
|
const reviewHead = yield* git.readRefOption(REVIEW_HEAD_REF);
|
|
206198
206287
|
const currentRest = yield* resolveRest(getOrUndefined(reviewHead));
|
|
206199
206288
|
const changes = yield* pendingChanges(git);
|
|
206289
|
+
const vars = resolveVars(config.workflowVars, config.rcVars, envVars.all);
|
|
206290
|
+
const renderedOn = yield* renderOnEdgesOrFail(currentRest.stateDef.on, vars);
|
|
206200
206291
|
const group = model.states.find((s) => s.name === currentRest.state)?.group;
|
|
206201
|
-
return buildCurrentStateModel(currentRest, changes, group);
|
|
206292
|
+
return buildCurrentStateModel(currentRest, changes, renderedOn, group);
|
|
206202
206293
|
});
|
|
206203
206294
|
/**
|
|
206204
206295
|
* `gtd visualize`: serve an interactive diagram of the ACTIVE workflow on a
|
package/docs/cli.md
CHANGED
|
@@ -45,6 +45,10 @@ Commands:
|
|
|
45
45
|
commits that would be lost
|
|
46
46
|
next Print the resolved rest's rendered script/prompt/message
|
|
47
47
|
(no mutation)
|
|
48
|
+
edit [path] Open <path> (repo-relative) in $VISUAL/$EDITOR, blocking
|
|
49
|
+
until the editor exits. With no argument, opens the
|
|
50
|
+
resolved rest's declared file (or the repo root if it
|
|
51
|
+
declares none). Never reads --no-edit/GTD_NO_EDIT
|
|
48
52
|
status Print the resolved rest's state/actor and which declared
|
|
49
53
|
pattern (if any) each pending change matches (no mutation)
|
|
50
54
|
validate Format and validate the steering file the resolved rest
|
|
@@ -63,6 +67,8 @@ Options:
|
|
|
63
67
|
--no-open (gtd visualize only) do not open the browser
|
|
64
68
|
--cost=<n> (gtd step only) record the invocation's token cost
|
|
65
69
|
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
70
|
+
--no-edit (bare gtd or gtd loop only) disable the loop's automatic
|
|
71
|
+
editor launching at human gates
|
|
66
72
|
--version, -v Print version and exit
|
|
67
73
|
--help, -h Print this help and exit
|
|
68
74
|
```
|
|
@@ -79,12 +85,32 @@ history relative to cwd, so it refuses with a clear error if invoked from a
|
|
|
79
85
|
subdirectory.
|
|
80
86
|
|
|
81
87
|
`--json`, `--cost=<n>`, and `--model=<name>` (the latter two only for
|
|
82
|
-
`gtd step`) are the only long options
|
|
83
|
-
like `--jsn`) is rejected with a usage error
|
|
84
|
-
mistyped flag can never degrade a JSON caller
|
|
85
|
-
`--cost`/`--model` with no value, a non-numeric or
|
|
86
|
-
`--model`, `--model` without `--cost`, or either
|
|
87
|
-
`gtd step` are all usage errors.
|
|
88
|
+
`gtd step`) are the only long options the compiled bundle recognizes. Any other
|
|
89
|
+
`--` option (including a typo like `--jsn`) is rejected with a usage error
|
|
90
|
+
rather than silently ignored, so a mistyped flag can never degrade a JSON caller
|
|
91
|
+
to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
|
|
92
|
+
negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
|
|
93
|
+
flag on any command other than `gtd step` are all usage errors.
|
|
94
|
+
|
|
95
|
+
`--no-edit` is a separate, bash-level flag handled entirely by `bin/gtd` itself,
|
|
96
|
+
stripped before anything reaches the bundle — see [`--no-edit`](#--no-edit)
|
|
97
|
+
below.
|
|
98
|
+
|
|
99
|
+
## `--no-edit`
|
|
100
|
+
|
|
101
|
+
A flag on the loop driver only — bare `gtd --no-edit` or `gtd loop --no-edit`
|
|
102
|
+
(the two are equivalent, since bare `gtd` and `gtd loop` are themselves
|
|
103
|
+
equivalent). Recognized in exactly those two positions; any other
|
|
104
|
+
placement/combination (e.g. `gtd step --no-edit`, or combined with any other
|
|
105
|
+
argument) is a usage error. The `GTD_NO_EDIT` environment variable (any
|
|
106
|
+
non-empty value) does the same thing.
|
|
107
|
+
|
|
108
|
+
Disables the loop's default behavior of launching an editor at human gates,
|
|
109
|
+
restoring the previous halt-and-print-and-exit behavior with no editor involved.
|
|
110
|
+
It governs only the loop's own automatic launching — `gtd edit` invoked directly
|
|
111
|
+
never reads it (see [`gtd edit`](#gtd-edit-path) above). See
|
|
112
|
+
[Driving the loop](loop.md) and `skills/loop/SKILL.md` for the full gate-flow
|
|
113
|
+
description.
|
|
88
114
|
|
|
89
115
|
## `gtd init`
|
|
90
116
|
|
|
@@ -408,6 +434,40 @@ which are JSON-only. `--json` emits
|
|
|
408
434
|
when the state has no `on` (a commit state); a per-edge `describe` is likewise
|
|
409
435
|
omitted when that edge declares none.
|
|
410
436
|
|
|
437
|
+
## `gtd edit [path]`
|
|
438
|
+
|
|
439
|
+
Handled entirely in `bin/gtd`'s own bash — unlike every other subcommand, it is
|
|
440
|
+
**never forwarded** to the compiled bundle (`dist/gtd.bundle.mjs`).
|
|
441
|
+
|
|
442
|
+
With `<path>` given (repo-relative), opens it in `${VISUAL:-$EDITOR}` (git's own
|
|
443
|
+
precedence — `$VISUAL` first, then `$EDITOR`; no fallback to `vi`), blocking in
|
|
444
|
+
the foreground until the editor exits. It creates nothing if `<path>` doesn't
|
|
445
|
+
exist — the path is handed straight to the editor command verbatim. The editor's
|
|
446
|
+
own exit code is ignored entirely; success is judged from tree state afterward,
|
|
447
|
+
not from this command.
|
|
448
|
+
|
|
449
|
+
With no argument, it peeks with `gtd next --json` to find the resolved rest's
|
|
450
|
+
declared `.file`, and opens that (repo-relative), or the repo directory (`.`)
|
|
451
|
+
when the resolved state declares no `file:`. If `gtd next --json` itself fails
|
|
452
|
+
(e.g. not in a repo), it prints an error and exits non-zero without attempting
|
|
453
|
+
to open an editor:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
gtd edit: could not determine the next step:
|
|
457
|
+
<gtd next --json's own error output>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`gtd edit` **never** reads `--no-edit`/`GTD_NO_EDIT` — those two govern only the
|
|
461
|
+
loop's own automatic editor launching at human gates (see
|
|
462
|
+
[Driving the loop](loop.md) and `skills/loop/SKILL.md`), not this command.
|
|
463
|
+
Invoked directly, `gtd edit` always launches, unconditionally.
|
|
464
|
+
|
|
465
|
+
If neither `$VISUAL` nor `$EDITOR` is set, no editor is launched:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
gtd: no editor configured — set $EDITOR (or $VISUAL)
|
|
469
|
+
```
|
|
470
|
+
|
|
411
471
|
## Running `script` rests (no `gtd` subcommand)
|
|
412
472
|
|
|
413
473
|
There is no `gtd` subcommand that executes a workflow script — gtd never runs a
|
package/docs/configuration.md
CHANGED
|
@@ -135,12 +135,12 @@ An `on` row's value is normally the target state name. It may instead be an
|
|
|
135
135
|
object `{ to: <target>, describe: <sentence> }`, attaching a human-readable
|
|
136
136
|
`describe` — a plain sentence explaining what making that kind of change does
|
|
137
137
|
next. `describe` is **inert to the engine**: it plays no part in matching, is
|
|
138
|
-
never Eta-rendered
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
gate's `message:` can render a "what each change does next"
|
|
143
|
-
routing the engine uses — one source of truth:
|
|
138
|
+
never Eta-rendered — UNLIKE the pattern key itself, which IS an Eta template
|
|
139
|
+
(see below) — and never affects a decision. It exists only to be surfaced. A
|
|
140
|
+
state's own edges are handed to its content template as `it.edges` (an array of
|
|
141
|
+
`{ pattern, target, describe? }`, `pattern` already rendered, in declaration
|
|
142
|
+
order), so a human gate's `message:` can render a "what each change does next"
|
|
143
|
+
list from the same routing the engine uses — one source of truth:
|
|
144
144
|
|
|
145
145
|
```yaml
|
|
146
146
|
workflow:
|
|
@@ -174,6 +174,14 @@ skipped by the `if (e.describe)` guard. `gtd next --json` and
|
|
|
174
174
|
no `on` (a commit state), and omit a per-edge `describe` when that edge declares
|
|
175
175
|
none.
|
|
176
176
|
|
|
177
|
+
**The pattern key (everything before the value) is an Eta template, rendered
|
|
178
|
+
against `it.vars` alone** — the same vars-only slice `on` templates could ever
|
|
179
|
+
need, since a pattern only ever names a path, never a diff or commit hash. A
|
|
180
|
+
literal path (`"A .gtd/FEEDBACK.md": fix`) still works unchanged; naming a var
|
|
181
|
+
instead (`"A <%= it.vars.feedbackFile %>": fix`) makes that row track a
|
|
182
|
+
repointed `feedbackFile` the same way `file:` does — see
|
|
183
|
+
["Variables"](#variables).
|
|
184
|
+
|
|
177
185
|
### `model:` — the opaque harness hint, template-rendered
|
|
178
186
|
|
|
179
187
|
A state may declare `model: <string>` — an OPAQUE label (e.g. `smart`, `fast`,
|
|
@@ -343,14 +351,19 @@ the built-in names only.
|
|
|
343
351
|
resolved state declares none — exactly like `model`. Plain `gtd status` prints
|
|
344
352
|
`File:`/`Mode:` lines (right after `Model:`, when present) when set.
|
|
345
353
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
+
**`on` pattern keys are Eta templates too.** A workflow's `on` patterns may name
|
|
355
|
+
a filename var the same way `file:`/content does — e.g.
|
|
356
|
+
`"A <%= it.vars.feedbackFile %>": fix` instead of a literal
|
|
357
|
+
`"A .gtd/FEEDBACK.md": fix`. The edge (`src/Edge.ts`'s `renderOnEdges`) renders
|
|
358
|
+
every pattern's key against `it.vars` ALONE (no diffs/commit hashes — a pattern
|
|
359
|
+
only ever needs to name a path) before handing the edges to the pure engine,
|
|
360
|
+
which only ever matches plain, already-rendered strings. This is what lets a
|
|
361
|
+
repointed filename var (`.gtdrc`'s top-level `vars:`, or a `GTD_VAR_` override)
|
|
362
|
+
reroute the machine consistently: `file:` (and any template reading/ writing
|
|
363
|
+
that path) and the `on` map that decides what a change to it MEANS both follow
|
|
364
|
+
the same var. A pattern written with a literal `.gtd/…` path instead of the var
|
|
365
|
+
still works exactly as before — only a workflow that templates its patterns
|
|
366
|
+
benefits from the repoint.
|
|
354
367
|
|
|
355
368
|
### `modes:` — pluggable steering-file modes
|
|
356
369
|
|
package/docs/loop.md
CHANGED
|
@@ -41,16 +41,35 @@ loop picks their change up.
|
|
|
41
41
|
|
|
42
42
|
**A `"message"` rest is a real handoff, not a slow beat.** No driver signs off
|
|
43
43
|
for the human: in the bundled workflow the loop runs the whole cycle unattended
|
|
44
|
-
up to `await-review` and then
|
|
44
|
+
up to `await-review` and then rests there with the review checkout window open
|
|
45
45
|
(the reviewable diff surfaced as uncommitted changes — see
|
|
46
46
|
[STATES.md §11](../STATES.md#11-the-review-checkout-window)). Advancing takes a
|
|
47
47
|
human edit to `.gtd/REVIEW.md`: ticking every box with **no** comment signs off
|
|
48
48
|
into the squash finale, while any comment (a note, or a code edit of your own)
|
|
49
|
-
is feedback that routes into another build + re-review round.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
|
-
commits and keeps their content as uncommitted changes.
|
|
49
|
+
is feedback that routes into another build + re-review round. That gate is the
|
|
50
|
+
contract, not a stall — nothing advances until the edit is captured, whether the
|
|
51
|
+
capture happens within the same run or a later one. A review nobody is going to
|
|
52
|
+
finish is ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
|
+
process's commits and keeps their content as uncommitted changes.
|
|
54
|
+
|
|
55
|
+
**Editing is on by default.** Reaching a `"message"` rest, `bin/gtd` opens
|
|
56
|
+
`${VISUAL:-$EDITOR}` on the rest's declared `.file` (or the repo root `.` when
|
|
57
|
+
the state declares none), blocking until the editor exits, then runs
|
|
58
|
+
`gtd step human` to capture whatever changed:
|
|
59
|
+
|
|
60
|
+
- a new commit was captured → the loop keeps driving on its own; re-launching is
|
|
61
|
+
never necessary;
|
|
62
|
+
- zero commits were captured (a clean tree once the editor closed) → the loop
|
|
63
|
+
halts, exit 0 — "done for now";
|
|
64
|
+
- the step refuses (a malformed steering file, or an edit matching no declared
|
|
65
|
+
pattern) → the refusal is surfaced verbatim and the loop halts non-zero,
|
|
66
|
+
exactly as a manual `gtd step human` refusal would.
|
|
67
|
+
|
|
68
|
+
Set `GTD_NO_EDIT` (any non-empty value), or pass `--no-edit` — see
|
|
69
|
+
[`cli.md`'s `--no-edit`](cli.md#--no-edit) for its exact syntax and positioning
|
|
70
|
+
— to disable this and restore the original halt-and-print behavior: the loop
|
|
71
|
+
prints the gate via `gtd next` and exits 0 without touching an editor, leaving
|
|
72
|
+
the human to edit and re-launch the loop themselves.
|
|
54
73
|
|
|
55
74
|
```bash
|
|
56
75
|
gtd next --json # ask who's up and what they should do
|
|
@@ -73,16 +92,18 @@ when the machine rests at a `"message"` gate — so `gtd` is the only command a
|
|
|
73
92
|
human runs. `bin/gtd` is the packaged entry point: invoked bare, or with `loop`
|
|
74
93
|
as its first argument, it runs this exact script; any other first argument (e.g.
|
|
75
94
|
`next`, `step`, `status`) hands off to `node dist/gtd.bundle.mjs` instead. The
|
|
76
|
-
loop body it runs adds
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
and
|
|
95
|
+
loop body it runs adds five things on top of the reference script above: it
|
|
96
|
+
opens an editor at every `"message"` gate instead of just printing it (see
|
|
97
|
+
"Editing is on by default" above; `--no-edit`/`GTD_NO_EDIT` fall back to the
|
|
98
|
+
reference script's plain print-and-exit); it stops with a diagnostic if the same
|
|
99
|
+
`"prompt"` state/content repeat with no progress (see `skills/loop/SKILL.md`'s
|
|
100
|
+
"Stall detection"); it lets `GTD_LOOP_AGENT_CMD` swap in any coding agent CLI in
|
|
101
|
+
place of the default `claude -p`, receiving the prompt via `$GTD_LOOP_PROMPT`;
|
|
102
|
+
it exports the resolved state's optional `model` hint as `$GTD_LOOP_MODEL`,
|
|
103
|
+
appending `--model "$GTD_LOOP_MODEL"` to the default `claude -p` invocation
|
|
104
|
+
whenever it's non-empty; and it acts on the optional `memory` scope hint (see
|
|
105
|
+
"Agent memory scope" below) — continuing one agent session across consecutive
|
|
106
|
+
same-scope turns and starting fresh when the scope changes.
|
|
86
107
|
|
|
87
108
|
`bin/gtd` resolves both its bundle hand-off and the loop's own internal `gtd`
|
|
88
109
|
calls against `dist/gtd.bundle.mjs` next to its own location. Set `GTD_BIN` — a
|