@pmelab/gtd 7.8.0 → 7.10.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 CHANGED
@@ -61,15 +61,16 @@ gtd init
61
61
  This writes a minimal `.gtdrc.json` seeding the one variable most projects
62
62
  change — the test command (`vars.testCommand`, defaulting to `npm test`) — plus
63
63
  a top-level `modes:` block suggesting **Prettier** as the steering-file
64
- formatter (`npx prettier --write` for the built-in `qa`/`review` modes — format
65
- only, so gtd still validates); edit or drop either freely (point `testCommand`
66
- at your suite, swap Prettier for dprint or a script, delete a key). It writes
67
- **no** `workflow:` key — the machine is built in — so review and commit the file
68
- before your first `gtd step`. `gtd init` takes no argument and refuses to
69
- clobber an existing config; it may also run in a plain parent directory (not a
70
- git repo) to seed a shared config a nested repo picks up. To customize the
71
- machine itself, add a `workflow:` key (there is no default fallback to merge
72
- over — a `workflow:` is the whole definition). See
64
+ formatter (`npx prettier --write` for the built-in `qa`/`review`/`prose` modes —
65
+ format only, so gtd still validates the `qa`/`review` ones; `prose`, used by the
66
+ simple flow's plan file, has no validator to begin with); edit or drop either
67
+ freely (point `testCommand` at your suite, swap Prettier for dprint or a script,
68
+ delete a key). It writes **no** `workflow:` key — the machine is built in — so
69
+ review and commit the file before your first `gtd step`. `gtd init` takes no
70
+ argument and refuses to clobber an existing config; it may also run in a plain
71
+ parent directory (not a git repo) to seed a shared config a nested repo picks
72
+ up. To customize the machine itself, add a `workflow:` key (there is no default
73
+ fallback to merge over — a `workflow:` is the whole definition). See
73
74
  [Configuration](docs/configuration.md#gtd-init) for the details.
74
75
 
75
76
  ## How it works
@@ -169,11 +170,10 @@ workflow is just `.gtdrc` config — edit it or write your own (see
169
170
  [Configuration](docs/configuration.md)). Every agent state routes its model
170
171
  through two `vars` tiers — `plannerModel` (heavier planning and review) and
171
172
  `coderModel` (the coding turns) — so you can repoint the models globally in one
172
- place (a `vars:` edit or a `GTD_VAR_plannerModel` override) instead of per
173
- state. Steering-file path vars (`feedbackFile`, `reviewFile`, …) work the same
174
- way, and now propagate to the `on` patterns that route on them too — a repointed
175
- path var actually reroutes the machine, not just the templates that read/write
176
- the file.
173
+ place (a `vars:` edit or a `GTD_PLANNERMODEL` override) instead of per state.
174
+ Steering-file path vars (`feedbackFile`, `reviewFile`, …) work the same way, and
175
+ now propagate to the `on` patterns that route on them too — a repointed path var
176
+ actually reroutes the machine, not just the templates that read/write the file.
177
177
 
178
178
  Bare `gtd` (or `gtd loop`) is a ready-to-run driver for the whole protocol —
179
179
  point it at a repo and it runs the loop until it's your turn. It is the only
@@ -186,11 +186,12 @@ to halting and printing the gate instead, if you'd rather edit and re-launch it
186
186
  yourself; pass `--edit`/`-e` to force the editor open at the gate right now,
187
187
  overriding an ambient `GTD_NO_EDIT`/`--no-edit`. Pass `--once` to restrict a run
188
188
  to exactly one beat — one human gate, one check, or one agent turn — instead of
189
- driving all the way to idle; it combines freely with `--edit`/`--no-edit`. Bare
190
- `gtd` prints one line per event — colored and emoji on a real terminal, plain
191
- ASCII under `NO_COLOR` or when piped — and redirects the noisier
192
- agent/check/step subprocess output to a per-repo/per-worktree log file.
193
- `gtd log` opens that logfile in your editor. See
189
+ driving all the way to idle; it combines freely with `--edit`/`--no-edit`. Pass
190
+ `--no-notify` (or set `GTD_NO_NOTIFY`) to suppress Herdr desktop notifications
191
+ while keeping Herdr sidebar state reporting on. Bare `gtd` prints one line per
192
+ event — colored and emoji on a real terminal, plain ASCII under `NO_COLOR` or
193
+ when piped — and redirects the noisier agent/check/step subprocess output to a
194
+ per-repo/per-worktree log file. `gtd log` opens that logfile in your editor. See
194
195
  [Driving the loop](docs/loop.md).
195
196
 
196
197
  Before wiring gtd into a repo, note the
@@ -206,12 +207,16 @@ diagnostics for both (live as you edit), and a `gtd.openSteeringFile` command
206
207
  that jumps to the current state's steering file. Config-driven via each state's
207
208
  `file:`/`mode:` (see [CLI reference](docs/cli.md#gtd-lsp)) — falls back to
208
209
  basename dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and
209
- `review` are gtd's built-in steering-file MODES — validators, not formatters: a
210
- mode's `format:` and `validate:` are shell commands a workflow (or a project's
211
- `.gtdrc`) declares for itself, so you bring your own formatter and your own
212
- checkers (see
213
- [Configuration](docs/configuration.md#modes--pluggable-steering-file-modes)).
214
- Both halves are enforced by `gtd validate` and the `gtd step` gate.
210
+ `review` are gtd's built-in steering-file MODES with a VALIDATOR gtd itself
211
+ implements; a mode's `format:` and `validate:` are shell commands a workflow (or
212
+ a project's `.gtdrc`) declares for itself, so you bring your own formatter and
213
+ your own checkers (see
214
+ [Configuration](docs/configuration.md#modes--pluggable-steering-file-modes)). A
215
+ third built-in, `prose` — format-only, no validator, used by the simple flow's
216
+ plan file — has no live editor support: `gtd lsp` publishes no symbols or
217
+ diagnostics for it, though `gtd validate` and the `gtd step` gate still format
218
+ and validate it like any other mode. Both halves are enforced by `gtd validate`
219
+ and the `gtd step` gate.
215
220
 
216
221
  Herdr integration: a workflow state can declare an optional `label:` — a
217
222
  human-readable display name surfaced in `gtd next --json`/`gtd status`. The
package/bin/gtd CHANGED
@@ -3,15 +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` / `--edit` (`-e`) / `--once` (bare, or immediately after
7
- # `loop`, in any order/combination — see the flag-parsing block below) ->
8
- # stripped here; `--no-edit` disables the loop's automatic editor
9
- # launching for this run, `--edit` forces it on for this run (overriding
10
- # an ambient `GTD_NO_EDIT`/`--no-edit` — the two are mutually exclusive),
11
- # and `--once` restricts the loop to exactly one beat (one human gate, one
12
- # script check+step, or one agent prompt+step) before exiting. Dispatch
13
- # then continues on the remaining arguments as if none of them had ever
14
- # been given.
6
+ # - `--no-edit` / `--edit` (`-e`) / `--once` / `--no-notify` (bare, or
7
+ # immediately after `loop`, in any order/combination — see the
8
+ # flag-parsing block below) -> stripped here; `--no-edit` disables the
9
+ # loop's automatic editor launching for this run, `--edit` forces it on
10
+ # for this run (overriding an ambient `GTD_NO_EDIT`/`--no-edit` — the two
11
+ # are mutually exclusive), `--once` restricts the loop to exactly one beat
12
+ # (one human gate, one script check+step, or one agent prompt+step) before
13
+ # exiting, and `--no-notify` (or `GTD_NO_NOTIFY`) suppresses the loop's
14
+ # Herdr desktop notifications for this run while leaving its silent Herdr
15
+ # pane-state reporting untouched. Dispatch then continues on the
16
+ # remaining arguments as if none of them had ever been given.
15
17
  # - `edit [path]` -> handled entirely here in bash, never forwarded to the
16
18
  # bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
17
19
  # resolved rest's `.file` (or `.` when absent). Low-level plumbing now that
@@ -224,22 +226,25 @@ resolve_log_path() {
224
226
  fi
225
227
  }
226
228
 
227
- # --no-edit / --edit (-e) / --once govern only the loop driver, recognised in
228
- # exactly two positions — bare (e.g. `gtd --edit --once`) or immediately after
229
- # `loop` (e.g. `gtd loop --once`) — in any order, each at most once. `--edit`
230
- # and `--no-edit` are mutually exclusive (forcing and suppressing the editor
231
- # in the same run is not a coherent request). A recognised, valid combination
232
- # is stripped here before anything below looks at "$@", so the bundle's own
233
- # unknown-option guard never sees it; anything else (an unknown flag, a
234
- # duplicate/conflicting one, or a flag mixed with a real subcommand) is left
235
- # untouched and falls through to the existing dispatch below, which forwards a
236
- # non-flag first argument to the bundle (whose own unknown-option guard
237
- # rejects it) or rejects a `loop` invocation carrying an unrecognised extra
238
- # argument.
229
+ # --no-edit / --edit (-e) / --once / --no-notify govern only the loop driver,
230
+ # recognised in exactly two positions — bare (e.g. `gtd --edit --once`) or
231
+ # immediately after `loop` (e.g. `gtd loop --once`) — in any order, each at
232
+ # most once. `--edit` and `--no-edit` are mutually exclusive (forcing and
233
+ # suppressing the editor in the same run is not a coherent request);
234
+ # `--no-notify` conflicts with nothing (it's orthogonal to editing and to
235
+ # `--once`). A recognised, valid combination is stripped here before anything
236
+ # below looks at "$@", so the bundle's own unknown-option guard never sees it;
237
+ # anything else (an unknown flag, a duplicate/conflicting one, or a flag mixed
238
+ # with a real subcommand) is left untouched and falls through to the existing
239
+ # dispatch below, which forwards a non-flag first argument to the bundle
240
+ # (whose own unknown-option guard rejects it) or rejects a `loop` invocation
241
+ # carrying an unrecognised extra argument.
239
242
  edit_disabled=0
240
243
  edit_forced=0
241
244
  once_mode=0
245
+ notify_disabled=0
242
246
  [[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
247
+ [[ -n "${GTD_NO_NOTIFY:-}" ]] && notify_disabled=1
243
248
 
244
249
  if [[ "${1:-}" == "loop" ]]; then
245
250
  loop_form=1
@@ -253,15 +258,17 @@ valid_combo=1
253
258
  n_no_edit=0
254
259
  n_edit=0
255
260
  n_once=0
261
+ n_no_notify=0
256
262
  for a in ${loop_flag_args[@]+"${loop_flag_args[@]}"}; do
257
263
  case "$a" in
258
264
  --no-edit) n_no_edit=$((n_no_edit + 1)) ;;
259
265
  --edit | -e) n_edit=$((n_edit + 1)) ;;
260
266
  --once) n_once=$((n_once + 1)) ;;
267
+ --no-notify) n_no_notify=$((n_no_notify + 1)) ;;
261
268
  *) valid_combo=0 ;;
262
269
  esac
263
270
  done
264
- if ((n_no_edit > 1 || n_edit > 1 || n_once > 1 || (n_no_edit >= 1 && n_edit >= 1))); then
271
+ if ((n_no_edit > 1 || n_edit > 1 || n_once > 1 || n_no_notify > 1 || (n_no_edit >= 1 && n_edit >= 1))); then
265
272
  valid_combo=0
266
273
  fi
267
274
 
@@ -269,6 +276,7 @@ if [[ "$valid_combo" == 1 ]]; then
269
276
  ((n_no_edit >= 1)) && edit_disabled=1
270
277
  ((n_edit >= 1)) && edit_forced=1
271
278
  ((n_once >= 1)) && once_mode=1
279
+ ((n_no_notify >= 1)) && notify_disabled=1
272
280
  # --edit overrides an ambient GTD_NO_EDIT/--no-edit for THIS run — the
273
281
  # explicit ask to force the editor wins over the suppressing default.
274
282
  ((edit_forced)) && edit_disabled=0
@@ -380,7 +388,11 @@ herdr_report() { # $1=state (working|blocked|idle) $2=message
380
388
  --source herdr:gtd --agent gtd --state "$1" --message "$2"
381
389
  }
382
390
 
391
+ # Suppressible via GTD_NO_NOTIFY/--no-notify (set into $notify_disabled at
392
+ # parse time, above) — the OS popup this call makes is the only thing that
393
+ # opts out; herdr_report/herdr_release (silent sidebar status) are unaffected.
383
394
  herdr_notify() { # $1=title $2=body
395
+ [[ "$notify_disabled" == 1 ]] && return 0
384
396
  herdr_ok || return 0
385
397
  herdr_run notification show "$1" --body "$2" --sound request
386
398
  }
@@ -189144,12 +189144,25 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
189144
189144
  var import_dist = require_dist$1();
189145
189145
  var import_dist$1 = require_dist();
189146
189146
  const BUILT_IN_MODES = ["qa", "review"];
189147
+ /**
189148
+ * Built-in mode names that ship NO validator at all — just a recognized name a
189149
+ * state's `mode:` may use without a `modes:` declaration, so it resolves to
189150
+ * "format if a `modes:` layer gives it one, validate nothing" (see
189151
+ * `src/SteeringMode.ts`'s `resolveSteeringMode`). `prose` is the one entry: a
189152
+ * curated free-form document with no gtd-side schema (the simple flow's plan
189153
+ * file), distinct from the schema'd `qa`/`review` built-ins.
189154
+ */
189155
+ const FORMAT_ONLY_BUILT_IN_MODES = ["prose"];
189156
+ /** Every built-in mode name — the validator tier (`BUILT_IN_MODES`) plus the format-only tier (`FORMAT_ONLY_BUILT_IN_MODES`) — used where a `mode:` just needs to be a KNOWN name, not necessarily one with a validator. */
189157
+ const KNOWN_BUILT_IN_MODES = [...BUILT_IN_MODES, ...FORMAT_ONLY_BUILT_IN_MODES];
189147
189158
  /** True when `mode` names one of gtd's own in-process implementations (see `BUILT_IN_MODES`) — the edge's dispatch, and a type guard so it can pick the parser. */
189148
189159
  const isBuiltInMode = (mode) => BUILT_IN_MODES.includes(mode);
189160
+ /** True when `mode` names any built-in — validator tier or format-only tier (see `KNOWN_BUILT_IN_MODES`) — without implying a validator exists. */
189161
+ const isKnownBuiltInMode = (mode) => KNOWN_BUILT_IN_MODES.includes(mode);
189149
189162
  /** The mode names the definition declares in `modes:` (empty when it declares none). */
189150
189163
  const declaredModes = (def) => Object.keys(def.modes ?? {});
189151
- /** Every mode name a state's `mode:` may legally name under `def`: the built-ins plus the declared ones (a declared name shadowing a built-in appears once). */
189152
- const knownModes = (def) => Array.from(/* @__PURE__ */ new Set([...BUILT_IN_MODES, ...declaredModes(def)]));
189164
+ /** Every mode name a state's `mode:` may legally name under `def`: the built-ins (validator and format-only tiers) plus the declared ones (a declared name shadowing a built-in appears once). */
189165
+ const knownModes = (def) => Array.from(/* @__PURE__ */ new Set([...KNOWN_BUILT_IN_MODES, ...declaredModes(def)]));
189153
189166
  /** Which content kind a state declares, or `undefined` if none (a validation error). */
189154
189167
  const contentKindOf = (state) => {
189155
189168
  if (state.script !== void 0) return "script";
@@ -189517,7 +189530,7 @@ const validateFile = (name, state) => {
189517
189530
  const validateMode = (def, name, state) => {
189518
189531
  if (state.mode === void 0) return [];
189519
189532
  const errors = [];
189520
- if (!knownModes(def).includes(state.mode)) errors.push(`state "${name}": "mode" must name a built-in mode (${BUILT_IN_MODES.join(", ")}) or one declared in "modes" (${declaredModes(def).length > 0 ? declaredModes(def).join(", ") : "none declared"}) (got "${state.mode}")`);
189533
+ if (!knownModes(def).includes(state.mode)) errors.push(`state "${name}": "mode" must name a built-in mode (${KNOWN_BUILT_IN_MODES.join(", ")}) or one declared in "modes" (${declaredModes(def).length > 0 ? declaredModes(def).join(", ") : "none declared"}) (got "${state.mode}")`);
189521
189534
  if (state.file === void 0) errors.push(`state "${name}": "mode" requires "file"`);
189522
189535
  if (isCommitState(state)) errors.push(`state "${name}": a commit state cannot declare "mode"`);
189523
189536
  return errors;
@@ -190084,7 +190097,7 @@ const collectGroups = (raw) => {
190084
190097
  * A sibling `vars:` key INSIDE the `workflow:` value declares the workflow's
190085
190098
  * OWN defaults for the merged `it.vars` template map (see
190086
190099
  * `PatternTemplates.TemplateContext.vars`) — the lowest-precedence of its
190087
- * three layers (a top-level `.gtdrc` `vars:` key, then `GTD_VAR_`-prefixed
190100
+ * three layers (a top-level `.gtdrc` `vars:` key, then `GTD_<UPPERCASE-name>`
190088
190101
  * environment variables, both assembled by `src/Edge.ts`'s `resolveVars`,
190089
190102
  * override it here). Every value must be a YAML scalar (string/number/
190090
190103
  * boolean) — `compileVarsMap` coerces it to a string; an object/array value
@@ -190594,7 +190607,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true) =
190594
190607
  };
190595
190608
  //#endregion
190596
190609
  //#region src/workflows/unified.yaml
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/`. The one\n file this prompt tells you to write is `<%= it.vars.reviewFile %>`;\n write it at exactly that path (it may live outside `.gtd/`), and touch\n nothing else under `.gtd/`.\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 file this round other than `.gtd/` or the\n # state's own reviewFile (which is var-configurable and may live outside\n # `.gtd/`, e.g. a root-level REVIEW.md — issue #128). 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 files did the human hand-edit this round, besides .gtd/ and the\n # reviewFile itself (var-configurable — may live outside .gtd/, so it is\n # excluded by exact path, not just the .gtd/ prefix)?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v '^\\.gtd/' | grep -vxF \"$reviewFile\")\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";
190610
+ 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_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 # Anchored here and aliased by `assertGreen` (the green-baseline gate,\n # ×3) and `fix-check` (`gtd fix` entry): one suite-check script shared\n # by all six call sites instead of five source copies.\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .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, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n script: *suiteCheck\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 mode: prose\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 mode: prose\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd 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/`. The one\n file this prompt tells you to write is `<%= it.vars.reviewFile %>`;\n write it at exactly that path (it may live outside `.gtd/`), and touch\n nothing else under `.gtd/`.\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 file this round other than `.gtd/` or the\n # state's own reviewFile (which is var-configurable and may live outside\n # `.gtd/`, e.g. a root-level REVIEW.md — issue #128). 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 files did the human hand-edit this round, besides .gtd/ and the\n # reviewFile itself (var-configurable — may live outside .gtd/, so it is\n # excluded by exact path, not just the .gtd/ prefix)?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v '^\\.gtd/' | grep -vxF \"$reviewFile\")\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 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 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 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: *suiteCheck\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";
190598
190611
  //#endregion
190599
190612
  //#region src/workflows/templates.ts
190600
190613
  /**
@@ -190615,18 +190628,21 @@ const INIT_VARS = { testCommand: "npm test" };
190615
190628
  /**
190616
190629
  * The top-level `modes:` block `gtd init` seeds into the scaffolded
190617
190630
  * `.gtdrc.json` as a ready-to-edit suggestion: a `format:` command for each
190618
- * built-in steering-file mode the bundled default uses (`qa` for the plan /
190619
- * open-questions files, `review` for REVIEW.md), so a fresh project's steering
190620
- * files are auto-formatted with Prettier before gtd validates them. Only
190621
- * `format:` is declared — gtd's built-in `qa`/`review` validators still do the
190622
- * validating (the two halves layer independently — see STATES.md §12 /
190631
+ * built-in steering-file mode the bundled default uses (`qa` for the
190632
+ * open-questions files, `review` for REVIEW.md, `prose` for the simple flow's
190633
+ * plan file), so a fresh project's steering files are auto-formatted with
190634
+ * Prettier before gtd validates them. Only `format:` is declared — gtd's
190635
+ * built-in `qa`/`review` validators still do the validating, and `prose` (a
190636
+ * format-only built-in — see `PatternMachine.isKnownBuiltInMode`) still
190637
+ * validates nothing (the two halves layer independently — see STATES.md §12 /
190623
190638
  * docs/configuration.md `modes:`). gtd ships no formatter, so this is the one
190624
190639
  * place a default is suggested; a project edits or drops it freely (swap
190625
190640
  * Prettier for dprint, point at a script, delete the key).
190626
190641
  */
190627
190642
  const MODES_SUGGESTION = {
190628
190643
  qa: { format: "npx prettier --write <%= it.file %>" },
190629
- review: { format: "npx prettier --write <%= it.file %>" }
190644
+ review: { format: "npx prettier --write <%= it.file %>" },
190645
+ prose: { format: "npx prettier --write <%= it.file %>" }
190630
190646
  };
190631
190647
  /**
190632
190648
  * The single bundled workflow template — the raw YAML text of `unified.yaml`,
@@ -191172,7 +191188,7 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
191172
191188
  * `WorktreeReader`: the one impure value (`process.env`) a caller needs is
191173
191189
  * handed in via `all`, never read directly, so `src/Edge.ts`'s `resolveVars`
191174
191190
  * (the three-layer `it.vars` merge — workflow default < `.gtdrc` `vars:` <
191175
- * `GTD_VAR_`-prefixed env, highest precedence) stays a pure function of its
191191
+ * `GTD_`-prefixed uppercase-name env, highest precedence) stays a pure function of its
191176
191192
  * arguments, and the in-memory test world can substitute its own env map
191177
191193
  * without ever touching the real `process.env`.
191178
191194
  */
@@ -192875,31 +192891,33 @@ const computeProcessRun = (git, def) => gen(function* () {
192875
192891
  costEntries
192876
192892
  };
192877
192893
  });
192878
- /** The `GTD_VAR_`-prefix stripped, exact-case, from every matching entry — the highest-precedence `it.vars` layer. A `value === undefined` entry (a name declared-but-unset in the environment) is skipped, never coerced to the string `"undefined"`. */
192879
- const envVarsFrom = (env) => {
192880
- const PREFIX = "GTD_VAR_";
192881
- const out = {};
192882
- for (const [key, value] of Object.entries(env)) {
192883
- if (value === void 0 || !key.startsWith(PREFIX)) continue;
192884
- out[key.slice(8)] = value;
192885
- }
192886
- return out;
192887
- };
192894
+ const PREFIX = "GTD_";
192888
192895
  /**
192889
192896
  * Assemble the merged `it.vars` map every template sees, from three layers
192890
192897
  * (later wins): the active workflow's own declared `vars:` defaults
192891
192898
  * (`ConfigOperations.workflowVars`), the top-level `.gtdrc` `vars:` key
192892
- * (`ConfigOperations.rcVars`), and every `GTD_VAR_`-prefixed environment
192893
- * variable (exact-case name match after the prefix) — the only layer that
192894
- * may introduce a name neither config layer declared. Pure: `env` is
192899
+ * (`ConfigOperations.rcVars`), and — for each name declared by either of
192900
+ * those two layers — a `GTD_<UPPERCASE-name>` environment variable, if
192901
+ * defined. Unlike the first two layers, the environment can only OVERRIDE a
192902
+ * name some config layer already declared; it can never introduce a new one
192903
+ * (an uppercased env key can't round-trip back to an arbitrary camelCase
192904
+ * name), so a `GTD_*` var matching no declared name is silently ignored. A
192905
+ * `value === undefined` entry (a name declared-but-unset in the environment)
192906
+ * is skipped, never coerced to the string `"undefined"`. Pure: `env` is
192895
192907
  * whatever the caller's `EnvVars` service handed it, never `process.env`
192896
192908
  * read directly here.
192897
192909
  */
192898
- const resolveVars = (workflowVars, rcVars, env) => ({
192899
- ...workflowVars,
192900
- ...rcVars,
192901
- ...envVarsFrom(env)
192902
- });
192910
+ const resolveVars = (workflowVars, rcVars, env) => {
192911
+ const merged = {
192912
+ ...workflowVars,
192913
+ ...rcVars
192914
+ };
192915
+ for (const name of Object.keys(merged)) {
192916
+ const value = env[PREFIX + name.toUpperCase()];
192917
+ if (value !== void 0) merged[name] = value;
192918
+ }
192919
+ return merged;
192920
+ };
192903
192921
  /** 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. */
192904
192922
  const toTemplateEdges = (edges) => (edges ?? []).map(([pattern, target, describe]) => describe !== void 0 ? {
192905
192923
  pattern,
@@ -204584,9 +204602,11 @@ const parseReviewDoc = (content) => {
204584
204602
  * `qa`-format file; a custom workflow that wants qa validation on TODO.md
204585
204603
  * declares it with `file:`+`mode: qa`, which the config-driven map covers.)
204586
204604
  *
204587
- * KNOWN LIMITATION — this server understands only the two BUILT-IN modes
204588
- * (`qa`/`review`, whose parsers it owns). A workflow-declared mode (a `modes:`
204589
- * entry, whose validation is a shell command — see `src/SteeringMode.ts`)
204605
+ * KNOWN LIMITATION — this server understands only the two VALIDATOR built-in
204606
+ * modes (`qa`/`review`, whose parsers it owns). A workflow-declared mode (a
204607
+ * `modes:` entry, whose validation is a shell command — see
204608
+ * `src/SteeringMode.ts`) — and the third built-in, `prose` (a format-only
204609
+ * name with no in-process parser — see `PatternMachine.isKnownBuiltInMode`) —
204590
204610
  * dispatches to no symbols, no code actions, and an empty diagnostic list: gtd
204591
204611
  * never runs a mode's command per keystroke over an unsaved buffer. Such a
204592
204612
  * file is still formatted and validated by `gtd validate` and the `gtd step`
@@ -205308,15 +205328,19 @@ const openInBrowser = (url) => {
205308
205328
  //#region src/SteeringMode.ts
205309
205329
  /**
205310
205330
  * Resolve a `mode:` name, half by half: a declared `format:`/`validate:` wins,
205311
- * and an undeclared `validate:` falls back to the built-in parser of the same
205312
- * name. `undefined` for a name that is neither declared nor built in —
205313
- * `validateDefinition` rejects that at load time, so the edge only ever sees it
205314
- * as a defensive case.
205331
+ * and an undeclared `validate:` falls back to the built-in PARSER of the same
205332
+ * name — only `qa`/`review` have one (`isBuiltInMode`). `prose` is a known
205333
+ * built-in NAME with no parser (`isKnownBuiltInMode`), so it resolves without
205334
+ * a declaration to `{ mode: "prose" }` — no format, no validate — and gains
205335
+ * formatting only, never validation, from a `modes:` layer. `undefined` for a
205336
+ * name that is neither declared nor a known built-in — `validateDefinition`
205337
+ * rejects that at load time, so the edge only ever sees it as a defensive
205338
+ * case.
205315
205339
  */
205316
205340
  const resolveSteeringMode = (def, mode) => {
205317
205341
  const declared = def.modes?.[mode];
205342
+ if (declared === void 0 && !isKnownBuiltInMode(mode)) return void 0;
205318
205343
  const builtIn = isBuiltInMode(mode);
205319
- if (declared === void 0 && !builtIn) return void 0;
205320
205344
  const validate = declared?.validate !== void 0 ? {
205321
205345
  kind: "command",
205322
205346
  command: declared.validate
@@ -88,7 +88,7 @@ workflow:
88
88
  model: <string> # optional, opaque harness hint — forbidden on a commit state
89
89
  memory: <string> # optional, opaque memory-scope label — forbidden on a commit state
90
90
  file: <string> # optional, an Eta template naming the state's steering file — forbidden on a commit state
91
- mode: <modeName> # optional, requires "file" — a built-in (qa/review) or a `modes:` entry; forbidden on a commit state
91
+ mode: <modeName> # optional, requires "file" — a built-in (qa/review/prose) or a `modes:` entry; forbidden on a commit state
92
92
  ```
93
93
 
94
94
  See [STATES.md](../STATES.md#1-the-model) for what each field means to the
@@ -332,10 +332,17 @@ implements itself:
332
332
  | `qa` | The open-questions format (`## Open Questions` near the top and `## Answered Questions` at the bottom, one `###` sub-heading per question with a free-form body; status is positional, no marker line). The bundled advanced flow additionally writes each open question as a checkbox list (candidate answers + a `- [ ] _your answer_` slot) and gates the human's answer step with `answerGate` (exactly one tick per question) — but that convention + gate live in the workflow/edge, not in this validator, which only checks the structure above. |
333
333
  | `review` | The checkbox review format (`# Review: <hash>` header, `<!-- base: <hash> -->` comment, `##` chunks, `- [ ]` pointers). |
334
334
 
335
+ A THIRD name, `prose`, is also built in but ships **no validator at all** — a
336
+ known mode name a state's `mode:` may use with no `modes:` declaration, that
337
+ formats (once a `modes:` layer gives it a `format:` command) but never
338
+ validates. It is for a curated, free-form document with no gtd-side schema — the
339
+ bundled template's simple-flow plan file (`.gtd/TODO.md` at
340
+ `planning`/`plan-review`) uses it.
341
+
335
342
  Any other name must be declared in a `modes:` map (next section) — as must a
336
- `format:` command for these two, since gtd ships no formatter. A `mode:` naming
337
- neither a built-in nor a declared mode is a load error listing both sets (typos
338
- must not silently disable the format gate or editor support).
343
+ `format:` command for any of the three built-ins, since gtd ships no formatter.
344
+ A `mode:` naming neither a built-in nor a declared mode is a load error listing
345
+ both sets (typos must not silently disable the format gate or editor support).
339
346
 
340
347
  What a mode buys the state: before capturing a turn out of it, `gtd step`
341
348
  formats that file in place and validates it, refusing the step when it is
@@ -358,7 +365,7 @@ a filename var the same way `file:`/content does — e.g.
358
365
  every pattern's key against `it.vars` ALONE (no diffs/commit hashes — a pattern
359
366
  only ever needs to name a path) before handing the edges to the pure engine,
360
367
  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)
368
+ repointed filename var (`.gtdrc`'s top-level `vars:`, or a `GTD_` override)
362
369
  reroute the machine consistently: `file:` (and any template reading/ writing
363
370
  that path) and the `on` map that decides what a change to it MEANS both follow
364
371
  the same var. A pattern written with a literal `.gtd/…` path instead of the var
@@ -416,9 +423,12 @@ declares it:
416
423
  1. the top-level `.gtdrc` `modes:` key (the project's own layer, cwd→home
417
424
  deep-merged like any other config key);
418
425
  2. the active workflow's own `modes:` map;
419
- 3. for the two built-in NAMES only, gtd's in-process validator — `qa` →
420
- `src/OpenQuestions.ts`, `review` → `src/ReviewDoc.ts`. There is no built-in
421
- formatter at this layer: **gtd ships no formatter**, so bring your own.
426
+ 3. for the two VALIDATOR built-in names only, gtd's in-process validator — `qa`
427
+ → `src/OpenQuestions.ts`, `review` → `src/ReviewDoc.ts`. The third built-in,
428
+ `prose`, has no in-process validator to fall back to, so it validates nothing
429
+ unless a `modes:` layer above declares its own `validate:`. There is no
430
+ built-in formatter at this layer for any of the three: **gtd ships no
431
+ formatter**, so bring your own.
422
432
 
423
433
  So for a state declaring `mode: qa`:
424
434
 
@@ -444,6 +454,7 @@ edit or drop it to taste. It is what a scaffolded `.gtdrc.json` contains
444
454
  "modes": {
445
455
  "qa": { "format": "npx prettier --write <%= it.file %>" },
446
456
  "review": { "format": "npx prettier --write <%= it.file %>" },
457
+ "prose": { "format": "npx prettier --write <%= it.file %>" },
447
458
  },
448
459
  }
449
460
  ```
@@ -451,7 +462,8 @@ edit or drop it to taste. It is what a scaffolded `.gtdrc.json` contains
451
462
  **Known limitation — no live editor support for a custom mode.** `gtd lsp`
452
463
  publishes diagnostics, document symbols and code actions for the built-in
453
464
  `qa`/`review` formats only (gtd never runs a mode command per keystroke over an
454
- unsaved buffer). A file whose validation is a command is still formatted and
465
+ unsaved buffer). A file whose validation is a command — and a `prose` file,
466
+ which has no validator to publish diagnostics for — is still formatted and
455
467
  validated by `gtd validate` and the `gtd step` capture gate.
456
468
 
457
469
  ### `reviewWindow:`/`reviewBase:` — the review checkout window
@@ -718,11 +730,13 @@ layers, **later wins**:
718
730
  inside it) — per-repo tuning without redefining the whole workflow. Subject
719
731
  to the same cwd→home deep merge as everything else in `.gtdrc` (innermost
720
732
  wins per-key).
721
- 3. **`GTD_VAR_<name>` environment variables** — highest precedence, checked at
722
- every invocation. The prefix is stripped and the REMAINING CASE matched
723
- exactly: `GTD_VAR_testCommand` sets `testCommand`, not `TESTCOMMAND` or
724
- `testcommand`. This is the only layer that may introduce a name neither
725
- config layer declared.
733
+ 3. **`GTD_<UPPERCASE-name>` environment variables** — highest precedence,
734
+ checked at every invocation, case-insensitively against each name already
735
+ declared by layer 1 or 2: `GTD_TESTCOMMAND` overrides `testCommand`. Unlike
736
+ the other two layers, the environment can only OVERRIDE a name a config layer
737
+ already declared — an uppercased env key can't round-trip back to an
738
+ arbitrary camelCase name, so a `GTD_*` var matching no declared name is
739
+ silently ignored.
726
740
 
727
741
  Values in layers 1–2 must be YAML scalars (string/number/boolean) — coerced to
728
742
  strings at load time; an object or array value is a load error, collected
@@ -740,7 +754,7 @@ vars:
740
754
  # highest precedence — beats both the workflow default and the .gtdrc value above.
741
755
  # The var is read at render time, so it shows up in the script `gtd next` prints
742
756
  # (and in what the loop driver then executes).
743
- GTD_VAR_testCommand="npm run test -- --bail" gtd next
757
+ GTD_TESTCOMMAND="npm run test -- --bail" gtd next
744
758
  ```
745
759
 
746
760
  A template reads any of it as `it.vars.<name>`:
@@ -753,7 +767,7 @@ workflow:
753
767
  working:
754
768
  actor: agent
755
769
  prompt: "Assigned reviewer: <%= it.vars.reviewer %>"
756
- model: "<%= it.vars.reviewModel %>" # a GTD_VAR_reviewModel override, or a .gtdrc vars: entry
770
+ model: "<%= it.vars.reviewModel %>" # a GTD_REVIEWMODEL override, or a .gtdrc vars: entry
757
771
  on:
758
772
  "* **": done
759
773
  ```
@@ -852,15 +866,18 @@ mode the default workflow uses:
852
866
  ```jsonc
853
867
  "modes": {
854
868
  "qa": { "format": "npx prettier --write <%= it.file %>" },
855
- "review": { "format": "npx prettier --write <%= it.file %>" }
869
+ "review": { "format": "npx prettier --write <%= it.file %>" },
870
+ "prose": { "format": "npx prettier --write <%= it.file %>" }
856
871
  }
857
872
  ```
858
873
 
859
874
  Only `format:` is declared, so gtd's built-in `qa`/`review` validators still do
860
- the validating (the two halves layer independently). gtd ships no formatter, so
861
- this is the one default it suggests: a fresh repo auto-formats its steering
862
- files out of the box, and you edit or drop the block freely (swap Prettier for
863
- dprint, point at a script, or delete the key).
875
+ the validating, and `prose` (a format-only built-in with no validator of its own
876
+ — see the `mode:` section above) still validates nothing (the two halves layer
877
+ independently). gtd ships no formatter, so this is the one default it suggests:
878
+ a fresh repo auto-formats its steering files out of the box, and you edit or
879
+ drop the block freely (swap Prettier for dprint, point at a script, or delete
880
+ the key).
864
881
 
865
882
  The built-in default workflow has three entry points into a shared review/squash
866
883
  tail (all walked through in
@@ -63,10 +63,10 @@ Prompts and scripts that already name these files switch to the vars
63
63
  (`<%~ it.vars.todoFile %>` etc.) so each filename has ONE source of truth in
64
64
  templates. **Known limitation, documented:** `on` pattern keys are NOT Eta
65
65
  templates — the default's patterns keep literal `.gtd/…` paths, so repointing a
66
- filename var (`.gtdrc`/`GTD_VAR_`) without also overriding the workflow's
67
- patterns desyncs the machine. The vars are a DRY mechanism inside templates and
68
- the state↔file association, not a rename switch. (Making pattern keys var-aware
69
- at compile time is noted as possible future work.)
66
+ filename var (`.gtdrc`/`GTD_`) without also overriding the workflow's patterns
67
+ desyncs the machine. The vars are a DRY mechanism inside templates and the
68
+ state↔file association, not a rename switch. (Making pattern keys var-aware at
69
+ compile time is noted as possible future work.)
70
70
 
71
71
  The advanced flow of the bundled unified template (see STATES.md §10) gains the
72
72
  same `file:`/`mode:` annotations (it is the LSP-heavy flow), including
@@ -81,8 +81,8 @@ same `file:`/`mode:` annotations (it is the LSP-heavy flow), including
81
81
  code path. Config is (re)loaded lazily per request (it is small); no watcher
82
82
  needed for v1.
83
83
  - **Path→mode dispatch:** compile the definition, render every state's `file:`
84
- (vars-layer context; the LSP process's env supplies `GTD_VAR_` overrides like
85
- any invocation), and build a map of absolute rendered paths → mode. Document
84
+ (vars-layer context; the LSP process's env supplies `GTD_` overrides like any
85
+ invocation), and build a map of absolute rendered paths → mode. Document
86
86
  features dispatch on that map: `qa` files get question symbols + parser-error
87
87
  diagnostics; `review` files get one headline symbol per chunk that still has
88
88
  an unchecked hunk (an outline of the packages left to review — no hunk
@@ -190,10 +190,10 @@ errors as load errors, closing gap #8 without executing anything impure
190
190
 
191
191
  One step further, the **undefined-variable lint**: scan template source for
192
192
  `it.vars.<name>` references and compare against the merged
193
- workflow-`vars:`/rc-`vars:` keys. Because a third layer (`GTD_VAR_*` env vars)
194
- can legitimately supply a name at run time, an unknown name is a **warning**,
195
- not an error. (A full "render with a tracking proxy and record misses" approach
196
- also works but executes template code at load — scanning the source is safer and
193
+ workflow-`vars:`/rc-`vars:` keys. Because a third layer (`GTD_*` env vars) can
194
+ legitimately supply a name at run time, an unknown name is a **warning**, not an
195
+ error. (A full "render with a tracking proxy and record misses" approach also
196
+ works but executes template code at load — scanning the source is safer and
197
197
  catches the common typo just as well.)
198
198
 
199
199
  ### 3.7 Domain cross-checks (gtd-specific lints)
package/docs/loop.md CHANGED
@@ -281,3 +281,9 @@ label tracked from the last completed iteration, when it fires outside the
281
281
  normal per-iteration flow. `report-agent` claims display authority for the
282
282
  `herdr:gtd` source, so `working` is re-reported every lap rather than once, to
283
283
  stay fresh against Herdr's own heuristic detection of the inner agent process.
284
+
285
+ Set `GTD_NO_NOTIFY` (any non-empty value) or pass `--no-notify` to suppress the
286
+ two `notification show` calls above (the human-gate halt and the exit trap) for
287
+ this run — the OS popup + sound only. `report-agent`/`release-agent` (the silent
288
+ Herdr sidebar status) are untouched either way, mirroring
289
+ `--no-edit`/`GTD_NO_EDIT`'s flag/env shape.
package/docs/upgrading.md CHANGED
@@ -42,7 +42,7 @@ upgrading.
42
42
  [Configuration](configuration.md)). A check's command now lives inline in its
43
43
  own `script:` content, reading a workflow-declared `it.vars` entry (the
44
44
  unified template's `testCommand`, overridable via the top-level `vars:` key or
45
- a `GTD_VAR_testCommand` environment variable — see
45
+ a `GTD_TESTCOMMAND` environment variable — see
46
46
  [Configuration's "Variables"](configuration.md#variables)) rather than a
47
47
  blessed `testCommand` config key; squashing is a `commit:` state instead of a
48
48
  boolean flag; there is no learning phase, no decision log, and no model
@@ -159,6 +159,22 @@ loudly at load time rather than silently misinterpreting.
159
159
  or agent harness, upgrading the `gtd` binary also means re-copying that skill
160
160
  from this release.
161
161
 
162
+ ## The var-override env prefix is now `GTD_<UPPERCASE-name>`, not `GTD_VAR_<name>`
163
+
164
+ The environment-variable layer of `it.vars` (see
165
+ [Configuration's "Variables"](configuration.md#variables)) changed its naming
166
+ scheme: the exact-case `GTD_VAR_<name>` prefix is gone, replaced by
167
+ `GTD_<UPPERCASE-name>` matched case-insensitively against each name already
168
+ declared by the workflow's own `vars:` or the top-level `.gtdrc` `vars:` key.
169
+ `GTD_VAR_testCommand` becomes `GTD_TESTCOMMAND`; `GTD_VAR_plannerModel` becomes
170
+ `GTD_PLANNERMODEL`.
171
+
172
+ This is also a narrowing, not just a rename: because an uppercased env key can't
173
+ round-trip back to an arbitrary camelCase name, the environment can no longer
174
+ introduce a var no config layer declared — a `GTD_*` env var matching no
175
+ declared name is now silently ignored, where it previously appeared verbatim
176
+ under its post-prefix name.
177
+
162
178
  ## 7.4: `gtd-loop` is gone — bare `gtd` and `gtd loop` run it directly
163
179
 
164
180
  `gtd-loop`, the standalone loop-driver binary installed alongside `gtd`, no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "7.8.0",
3
+ "version": "7.10.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {