@pmelab/gtd 9.10.0 → 9.10.1

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
@@ -202,50 +202,49 @@ The process converges on that same shared tail: an agent hands you a
202
202
  `.gtd/REVIEW.md` checkbox review of the diff — the prompt never inlines the diff
203
203
  itself; it names the commit the changes are based at and the agent runs
204
204
  `git diff` to read the range before writing the review. Each hunk is a
205
- `- [ ] ./path/to/file.ts#42` pointer with its explanation on the line(s) BELOW
206
- it, not trailing the pointer on the same line:
205
+ `- [ ] ./path/to/file.ts#42` pointer, and its explanation can either sit on the
206
+ line(s) below it or trail the pointer on the same line — both forms are equally
207
+ valid:
207
208
 
208
209
  ```
209
210
  - [ ] ./path/to/file.ts#42
210
211
  what this hunk does, with room to run to several lines
212
+ - [ ] ./path/to/file.ts#99 — a same-line note works too
211
213
  ```
212
214
 
213
- Text after the pointer token on the pointer's own line is a validation error —
214
- `gtd check`/`gtd validate`/the LSP all reject it — and, because the review
215
- gate's own emitted script runs that same check ahead of every commit, upgrading
216
- gtd mid-review leaves an already-open `.gtd/REVIEW.md` written in the old
217
- same-line form UNLANDABLE until every pointer in it is hand-edited into the new
218
- shape; there is no migration tool. If a styled `.gtd/REVIEW.md` ever comes out
219
- malformed some other way (a paragraph where a `- [ ] ./path#line` row belongs),
220
- `gtd check review` refuses the step the same way: the file stays exactly as
221
- written in the working tree, unlanded, and re-running the same prompt is the
222
- recovery — nothing is lost. While the process rests at that gate, the landing
223
- script opens a **review checkout window**: HEAD is rewound to the review base
224
- with the working tree untouched, so the whole reviewable change shows up as
225
- ordinary uncommitted changes in your editor's normal git integration (and files
226
- added during the process show up as ordinary untracked files, so discarding one
227
- deletes it — an untracked file you leave alone is not a pending change, and only
228
- actually removing it from disk counts as a deletion). The next landing's own
229
- script closes the window before it commits. Tick a box as you review each hunk
230
- (ticking just records "I read this"), and leave a **comment** to request
231
- changes: a note on a line, an inline `// TODO`-style comment in the code, or a
232
- direct code edit. A comment sends a FULL development lap, not a quick
233
- fix-and-re-review: an agent first judges whether your comment is actually
234
- actionable (a genuinely approving remark with no code edit short-circuits
235
- straight to sign-off instead), and an actionable round is re-planned from
236
- scratch through triage, architecture, and the package queue again — a hand-edit
237
- you made during review is treated as a **sketch**, the same as any other change
238
- that starts a process, not a fix the agent builds on: it is reverted out of the
239
- tree first, and your intent survives only in your own review-round commit for
240
- triage to read. There is no baseline check on the way back into planning. Only
241
- the turn that drafts the final squash message resumes the session that built the
242
- feature in the first place (the review tail is nested inside that build identity
243
- rather than sitting beside it) — an actionable round leaves that identity
244
- entirely, so it starts a fresh session like any other process. A comment is what
245
- asks for changes; landing with no comment signs off, whatever the boxes say,
246
- which collapses the whole process into one commit (a **squash finale** whose
247
- message an agent drafts). Deleting `.gtd/REVIEW.md` is refused — the guard only
248
- asks whether the step's diff deletes the file.
215
+ The one rule an explanation must follow either way: it must never itself START
216
+ with a bare `./path` token, since that parses as a second hunk pointer rather
217
+ than a note. If a styled `.gtd/REVIEW.md` ever comes out malformed some other
218
+ way (a paragraph where a `- [ ] ./path#line` row belongs), `gtd check review`
219
+ refuses the step the same way: the file stays exactly as written in the working
220
+ tree, unlanded, and re-running the same prompt is the recovery — nothing is
221
+ lost. While the process rests at that gate, the landing script opens a **review
222
+ checkout window**: HEAD is rewound to the review base with the working tree
223
+ untouched, so the whole reviewable change shows up as ordinary uncommitted
224
+ changes in your editor's normal git integration (and files added during the
225
+ process show up as ordinary untracked files, so discarding one deletes it — an
226
+ untracked file you leave alone is not a pending change, and only actually
227
+ removing it from disk counts as a deletion). The next landing's own script
228
+ closes the window before it commits. Tick a box as you review each hunk (ticking
229
+ just records "I read this"), and leave a **comment** to request changes: a note
230
+ on a line, an inline `// TODO`-style comment in the code, or a direct code edit.
231
+ A comment sends a FULL development lap, not a quick fix-and-re-review: an agent
232
+ first judges whether your comment is actually actionable (a genuinely approving
233
+ remark with no code edit short-circuits straight to sign-off instead), and an
234
+ actionable round is re-planned from scratch through triage, architecture, and
235
+ the package queue again — a hand-edit you made during review is treated as a
236
+ **sketch**, the same as any other change that starts a process, not a fix the
237
+ agent builds on: it is reverted out of the tree first, and your intent survives
238
+ only in your own review-round commit for triage to read. There is no baseline
239
+ check on the way back into planning. Only the turn that drafts the final squash
240
+ message resumes the session that built the feature in the first place (the
241
+ review tail is nested inside that build identity rather than sitting beside it)
242
+ — an actionable round leaves that identity entirely, so it starts a fresh
243
+ session like any other process. A comment is what asks for changes; landing with
244
+ no comment signs off, whatever the boxes say, which collapses the whole process
245
+ into one commit (a **squash finale** whose message an agent drafts). Deleting
246
+ `.gtd/REVIEW.md` is refused — the guard only asks whether the step's diff
247
+ deletes the file.
249
248
 
250
249
  A second entry, the same review tail's own direct entry point —
251
250
  `gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
@@ -1030,15 +1029,32 @@ After an agent turn at a state that declares `file:`+`mode:`, run the script:
1030
1029
  either `gtd next --json`'s own embedded `.validate` field (present at every
1031
1030
  `prompt` beat that hands over a validatable file), or, standalone,
1032
1031
  `gtd validate`'s own plain-text output — both resolve the exact same script from
1033
- one shared resolver. Exit 0 means the file is well-formed — proceed to
1034
- `gtd land`. A non-zero exit means the script's own captured output IS a
1035
- complete, ready-to-send fix prompt (an instruction plus the findings, see
1036
- `src/Emit.ts`'s `failurePromptWrapper`): send it back to the same agent session
1037
- verbatim, and cap how many fix attempts you allow yourself — the driver owns
1038
- that retry count, not gtd. Landing never TRUSTS that you validated, either: the
1039
- emitted `land` script carries the same format/validate commands ahead of its own
1040
- commit and fails without committing when they fail, so a malformed file is never
1041
- captured whether or not you ran the validate script first.
1032
+ one shared resolver. This field is now populated even at a FIRST-WRITE beat,
1033
+ before the steering file exists at all: the script itself carries a leading
1034
+ `[ -f <file> ] || exit 0` guard rather than gtd checking existence ahead of
1035
+ time, so a driver's `while [ -n "$gtd_validate" ]` repair loop is armed from the
1036
+ very first turn at a state, not just the second and later ones. Exit 0 means the
1037
+ file is well-formed (or genuinely doesn't exist yet) — proceed to `gtd land`. A
1038
+ non-zero exit usually means the script's own captured output IS a complete,
1039
+ ready-to-send fix prompt (an instruction plus the findings, see `src/Emit.ts`'s
1040
+ `failurePromptWrapper`): send it back to the same agent session verbatim, and
1041
+ cap how many fix attempts you allow yourself — the driver owns that retry count,
1042
+ not gtd. Landing never TRUSTS that you validated, either: the emitted `land`
1043
+ script carries the same format/validate commands ahead of its own commit and
1044
+ fails without committing when they fail, so a malformed file is never captured
1045
+ whether or not you ran the validate script first.
1046
+
1047
+ A non-zero exit can ALSO mean something the fix loop cannot fix: a mode whose
1048
+ `format:` command breaks its own validator, a config bug gtd detects by
1049
+ formatting a canonical sample and re-checking it before the real file's own
1050
+ findings are ever reported. That message is unmistakably different — it says
1051
+ this is a CONFIGURATION BUG, not the steering file, and tells the agent to stop
1052
+ and end its turn rather than edit anything. gtd's exit-code contract stays
1053
+ closed at five numbers (below), so nothing distinguishes the two cases at the
1054
+ process level — a driver's fix loop is still the CORRECT thing to run in both
1055
+ cases, it just can never win a contradiction: its 3-attempt cap is what ends
1056
+ one, not a fix. Reading the message text before re-prompting is what keeps an
1057
+ agent from thrashing on a document it cannot fix.
1042
1058
 
1043
1059
  ### Failure taxonomy and recovery
1044
1060
 
@@ -8,7 +8,7 @@ import { createHash, randomUUID } from "node:crypto";
8
8
  import * as NFS from "node:fs";
9
9
  import { existsSync, readFileSync } from "node:fs";
10
10
  import * as OS from "node:os";
11
- import { homedir } from "node:os";
11
+ import { homedir, tmpdir } from "node:os";
12
12
  import * as Path from "node:path";
13
13
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
14
14
  import * as ChildProcess from "node:child_process";
@@ -191036,6 +191036,22 @@ const flattenMachines = (raw, errors) => {
191036
191036
  scopes
191037
191037
  };
191038
191038
  };
191039
+ /**
191040
+ * `QA_FORMAT`'s canonical sample (see `SteeringFormat.sample`'s doc comment) —
191041
+ * a minimal, valid `qa`-mode document: one open question with two options plus
191042
+ * the unfilled free-text slot. Deliberately not authored to survive any
191043
+ * particular formatter.
191044
+ */
191045
+ const QA_SAMPLE = `Sample plan. Add a thing.
191046
+
191047
+ ## Open Questions
191048
+
191049
+ ### Which option?
191050
+
191051
+ - [ ] Option A
191052
+ - [ ] Option B
191053
+ - [ ] _your answer_
191054
+ `;
191039
191055
  const OPEN_QUESTIONS_HEADING = "## Open Questions";
191040
191056
  const ANSWERED_QUESTIONS_HEADING = "## Answered Questions";
191041
191057
  /**
@@ -191309,20 +191325,35 @@ const questionActions = (content, range) => {
191309
191325
  };
191310
191326
  /** The `qa` steering format: gtd's own in-process open-questions checkbox format — validation, outline, and code actions, no `pointerAt` (an open question's options have nothing to jump to). Its one finding is always positionless — this format has no notion of a per-line problem. */
191311
191327
  const QA_FORMAT = {
191328
+ sample: QA_SAMPLE,
191312
191329
  validate: (content) => parseOpenQuestions(content).errors.map((message) => ({ message })),
191313
191330
  outline: questionsOutline,
191314
191331
  actions: questionActions
191315
191332
  };
191316
191333
  //#endregion
191317
191334
  //#region src/ReviewDoc.ts
191335
+ /**
191336
+ * `REVIEW_FORMAT`'s canonical sample (see `SteeringFormat.sample`'s doc
191337
+ * comment) — a minimal, valid `review`-mode document: the header, the base
191338
+ * comment, and one chunk with one file pointer. Deliberately not authored to
191339
+ * survive any particular formatter.
191340
+ */
191341
+ const REVIEW_SAMPLE = `# Review: sample123
191342
+ <!-- base: 0000000000000000000000000000000000000000 -->
191343
+
191344
+ ## Sample chunk
191345
+
191346
+ - [ ] ./sample.ts#1
191347
+ what this hunk does
191348
+ `;
191318
191349
  const HEADER_RE = /^#\s+Review:\s*(\S+)\s*$/;
191319
191350
  const BASE_COMMENT_RE = /^<!--\s*base:\s*(\S+)\s*-->$/;
191320
191351
  const CHUNK_HEADING_RE = /^##\s+(.+)$/;
191321
- /** One `- [ ]`/`- [x]` pointer line: the box, the whitespace-delimited pointer token, and (invalidly) whatever trails it. */
191352
+ /** One `- [ ]`/`- [x]` pointer line: the box, the whitespace-delimited pointer token, and the inline note segment that trails it. */
191322
191353
  const FILE_POINTER_RE = /^-\s*\[([ xX])\]\s*(\S+)(?:\s+(.*))?$/;
191323
191354
  /** A pointer token's trailing `#<line>` (greedy, so a `#` inside the path stays in it). */
191324
191355
  const POINTER_LINE_RE = /^(.*)#(\d+)$/;
191325
- /** The optional dash a continuation line may lead with — em dash, en dash, or hyphens. */
191356
+ /** The optional dash that may lead the inline note segment or a continuation line — em dash, en dash, or hyphens. */
191326
191357
  const NOTE_SEPARATOR_RE = /^[—–-]+\s*/;
191327
191358
  /** The `# Review: <hash>` header, required as the document's first non-blank line. */
191328
191359
  const parseHeader = (lines) => {
@@ -191336,22 +191367,27 @@ const parseBaseComment = (lines) => {
191336
191367
  if (match) return match[1];
191337
191368
  }
191338
191369
  };
191339
- /** One `- [ ]` / `- [x]` file-pointer line, or `undefined` if `line` isn't one. The regex is unchanged from the legacy same-line-note form — a line WITH trailing text still parses, so the resulting error can point at it precisely (see the module docstring). */
191370
+ /** True when `token` parses as a file pointer's path token — leading `./`, and a path longer than two characters once an optional `#<line>` suffix is stripped. The single source of truth for "is this a pointer token", shared by `parseFilePointer` and the second-pointer finding below so the two can never drift apart. */
191371
+ const isPointerToken = (token) => {
191372
+ if (!token.startsWith("./")) return false;
191373
+ const lineMatch = POINTER_LINE_RE.exec(token);
191374
+ return (lineMatch ? lineMatch[1] : token).length > 2;
191375
+ };
191376
+ /** One `- [ ]` / `- [x]` file-pointer line, or `undefined` if `line` isn't one. Text trailing the pointer token on its own line still parses — it becomes the note's inline segment (see the module docstring), not an error. */
191340
191377
  const parseFilePointer = (line, sourceLine) => {
191341
191378
  const match = FILE_POINTER_RE.exec(line);
191342
191379
  if (!match) return void 0;
191343
191380
  const token = match[2];
191344
- if (!token.startsWith("./")) return void 0;
191381
+ if (!isPointerToken(token)) return void 0;
191345
191382
  const lineMatch = POINTER_LINE_RE.exec(token);
191346
191383
  const path = lineMatch ? lineMatch[1] : token;
191347
- if (path.length <= 2) return void 0;
191348
- const trailingText = (match[3] ?? "").trim();
191384
+ const inlineNote = (match[3] ?? "").trim();
191349
191385
  return {
191350
191386
  checked: match[1] !== " ",
191351
191387
  path,
191352
191388
  ...lineMatch ? { line: Number(lineMatch[2]) } : {},
191353
191389
  sourceLine,
191354
- ...trailingText.length > 0 ? { trailingText } : {}
191390
+ ...inlineNote.length > 0 ? { inlineNote } : {}
191355
191391
  };
191356
191392
  };
191357
191393
  /**
@@ -191374,13 +191410,6 @@ const pointerEndIndex = (body, index) => {
191374
191410
  }
191375
191411
  return end;
191376
191412
  };
191377
- /** The trailing-text validation error for one pointer whose own line carries text after the pointer token. */
191378
- const trailingTextError = (title, pointer) => {
191379
- return {
191380
- message: `Chunk "${title}" hunk ${pointer.line !== void 0 ? `${pointer.path}#${pointer.line}` : pointer.path} has text on the pointer line — move the explanation to the line(s) below it`,
191381
- line: pointer.sourceLine
191382
- };
191383
- };
191384
191413
  /** Joins a pointer's gathered explanation: every non-blank line strictly between `startIndex` and `endIndex` (inclusive), each stripped of a leading dash, joined with a single space. */
191385
191414
  const gatherNote = (body, startIndex, endIndex) => {
191386
191415
  const noteLines = [];
@@ -191390,19 +191419,48 @@ const gatherNote = (body, startIndex, endIndex) => {
191390
191419
  }
191391
191420
  return noteLines.join(" ").trim();
191392
191421
  };
191393
- /** One pointer's parsed `ReviewFile` (span + gathered note), its trailing-text error when its own line carried one, and the body index just past its span for the caller to resume from. */
191422
+ /**
191423
+ * The second-pointer validation error: a note whose first token is itself a
191424
+ * pointer token. The relaxed same-line form would otherwise silently read
191425
+ * `- [ ] ./a.ts#1 ./b.ts#2` (or with a separator, `- [ ] ./a.ts#1 — ./b.ts#2`)
191426
+ * as ONE pointer whose note is `./b.ts#2`, dropping the second hunk entirely —
191427
+ * this is the one case the relaxed parser must still refuse, and it points at
191428
+ * the pointer's own `sourceLine` so it renders as a positioned finding.
191429
+ */
191430
+ const secondPointerError = (title, pointer, secondToken) => {
191431
+ return {
191432
+ message: `Chunk "${title}" hunk ${pointer.line !== void 0 ? `${pointer.path}#${pointer.line}` : pointer.path}'s note starts with a second pointer (${secondToken}) — give it its own "- [ ]" line`,
191433
+ line: pointer.sourceLine
191434
+ };
191435
+ };
191436
+ /**
191437
+ * One pointer's parsed `ReviewFile` — its note composed from the inline
191438
+ * (same-line) segment first, then the lines gathered from below it, joined
191439
+ * with a single space — plus the second-pointer finding when the inline
191440
+ * segment itself opens with a pointer token, and the body index just past
191441
+ * this pointer's span for the caller to resume from.
191442
+ *
191443
+ * The second-pointer check is scoped to the inline segment ALONE, never the
191444
+ * composed note: a below-pointer explanation that legitimately opens with a
191445
+ * path (`./src/foo.ts is the caller`) must not be refused — refusing a
191446
+ * legitimate explanation is exactly the class of hard stop this format
191447
+ * relaxation exists to remove.
191448
+ */
191394
191449
  const parsePointerSpan = (title, body, index, pointer) => {
191395
191450
  const endIndex = pointerEndIndex(body, index);
191396
- const note = gatherNote(body, index, endIndex);
191397
- const { trailingText, ...pointerFields } = pointer;
191451
+ const inlineSegment = (pointer.inlineNote ?? "").replace(NOTE_SEPARATOR_RE, "");
191452
+ const note = [inlineSegment, gatherNote(body, index, endIndex)].filter((segment) => segment.length > 0).join(" ");
191453
+ const { inlineNote: _inlineNote, ...pointerFields } = pointer;
191454
+ const file = {
191455
+ ...pointerFields,
191456
+ endLine: body[endIndex].line,
191457
+ ...note.length > 0 ? { note } : {}
191458
+ };
191459
+ const secondToken = inlineSegment.split(/\s+/)[0] ?? "";
191460
+ const error = isPointerToken(secondToken) ? secondPointerError(title, pointer, secondToken) : void 0;
191398
191461
  return {
191399
- file: {
191400
- ...pointerFields,
191401
- endLine: body[endIndex].line,
191402
- ...note.length > 0 ? { note } : {},
191403
- ...trailingText !== void 0 ? { trailingText } : {}
191404
- },
191405
- ...trailingText !== void 0 ? { error: trailingTextError(title, pointer) } : {},
191462
+ file,
191463
+ ...error ? { error } : {},
191406
191464
  nextIndex: endIndex + 1
191407
191465
  };
191408
191466
  };
@@ -191464,7 +191522,7 @@ const splitChunks = (lines) => {
191464
191522
  }
191465
191523
  return chunks;
191466
191524
  };
191467
- /** Parses every `##` chunk into a `Changeset`, collecting one error per chunk with no file pointers plus each chunk body's own trailing-text errors. */
191525
+ /** Parses every `##` chunk into a `Changeset`, collecting one error per chunk with no file pointers plus each chunk body's own second-pointer errors. */
191468
191526
  const parseChangesets = (lines) => {
191469
191527
  const changesets = [];
191470
191528
  const errors = [];
@@ -191663,6 +191721,7 @@ const reviewPointerAt = (content, line) => {
191663
191721
  * module that otherwise imports none.
191664
191722
  */
191665
191723
  const STEERING_FORMATS = /* @__PURE__ */ new Map([["qa", QA_FORMAT], ["review", {
191724
+ sample: REVIEW_SAMPLE,
191666
191725
  validate: (content) => parseReviewFindings(content).findings,
191667
191726
  outline: reviewOutline,
191668
191727
  actions: reviewActions,
@@ -192518,7 +192577,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true, f
192518
192577
  };
192519
192578
  //#endregion
192520
192579
  //#region src/workflows/unified.yaml
192521
- var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceMachineFields`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`).\n#\n# * entryGate (dedup ×2) — the green-baseline gate (check + human blocked)\n# guarding the single start (`start-gate`) and\n# the review (`review-gate`) entries. Its check\n# script IS the shared `&suiteCheck` anchor\n# (also aliased by `fix-precheck`, outside this\n# machine) — every entry blocks on any red tree,\n# full stop; there is no baseline-aware second\n# run any more, since `unwind` (below) already\n# put the tree back to the baseline before this\n# gate ever runs. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# build tail's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * questionGate (dedup ×2) — the check/answer pair shared by the design and\n# architecture phases' own open-questions gate\n# (`design.gate.*` / `architecture.gate.*`): a\n# shared probe script decides whether the\n# phase's steering file still has unanswered\n# questions, and only then rests the process at\n# a human answer gate. Identity-free.\n# * designPlan (encap) — the triage phase's one conversation: group the\n# diff that started the process into ordered,\n# classified concerns and resolve the PRODUCT\n# ones' open questions that clear the bar — a\n# genuine fork, not already settled, worth\n# surviving to review — deciding every\n# sub-threshold point itself instead\n# (`design.*`: triage, gate.check,\n# gate.answer). ▸ planner.\n# * archPlan (encap) — the technical phase's one conversation: a COLD\n# read of the settled concerns, resolving the\n# TECHNICAL open questions that clear the same\n# bar (same decide-it-yourself sink below it),\n# then a mechanical decomposition into package\n# files (`architecture.*`: author, gate.check,\n# gate.answer, decompose). A separate machine\n# from `designPlan` (not one fused machine) —\n# its own memory scope, since there may be a\n# handover between the user-facing and the\n# technical phase. ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `buildTail` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — `collecting` only judges\n# whether the round is actionable; there is no\n# coder-identity follow-through inside this\n# machine any more (see the root's own\n# `re-unwind` state).\n# * specReview (encap) — the per-package agentic review, a SINGLE\n# state (`packages.item.spec.review`) — a\n# machine whose entire job is to be a separate\n# mind from the implementer. ▸ planner — fixing\n# the reviewer's own findings is a coder action,\n# so it lives in `packages.item.fix-spec`\n# instead.\n# * packageLoop (encap) — the per-package build queue\n# (`packages.picking`). Identity-free — the\n# actual per-package build identity lives in its\n# own `item` child (packageItem, below).\n# * packageItem (encap) — the per-package build identity (`packages.\n# item.*`: building, fix-suite, fix-spec,\n# closing), plus its own `health` and `spec`\n# children. ▸ coder.\n# * buildTail (encap) — the process's build identity (`build.*`:\n# squashing, fix), plus its own `health` child\n# AND its own `review` child\n# (humanReview, above). No monolithic \"build the\n# whole plan\" state any more — every package is\n# built inside `packages.item`, and the queue\n# drains straight into `build.review`. ▸ coder.\n#\n# ── Why humanReview is nested, not a root sibling ───────────────────────────\n#\n# `humanReview` sits UNDER `buildTail` (`build.review`) rather than beside it\n# at the root, because a machine's memory scope is its dotted instance path\n# (see src/PatternMachine.ts's `memoryScopeAt`): entering a DESCENDANT scope\n# never breaks the parent's unbroken run, but entering a sibling does. On the\n# ORDINARY path (packages -> build.review -> squashing) that buys nothing —\n# `build`'s own scope has no prior turn to resume, since every package is\n# built inside `packages.item` instead. The one concrete case where it\n# matters is `gtd --entry fix-precheck`: a red suite drops into `build.fix`,\n# which opens `build`'s own scope, and the whole run from there —\n# `build.fix` -> `build.health.check` -> `build.review.*` -> `build.squashing`\n# — stays inside that one subtree, so `build.squashing` drafts the finale\n# from the SAME session that made the fixes, instead of a root-level sibling\n# forcing it to re-derive the range from scratch on every pass through the\n# tail. That one benefit is worth keeping over hoisting `humanReview` back to\n# a root sibling, which would otherwise be a repo-wide rename with no\n# behavioural gain. (The OTHER coder turn a feedback round used to resume —\n# the implementer's own follow-through on review feedback — is retired\n# outright: a hand-edit made during review is now re-planned from scratch via\n# the root's own `re-unwind` state, never built upon, so there is no\n# continuity to preserve on that path any more.)\n# The reviewer itself is unaffected either way: `build.review.*` is its own\n# scope regardless of where it's nested, so it never resumes the builder's\n# session.\n#\n# ── One flow: triage into concerns, then packages, then the shared tail ────\n#\n# ANY change to the tree starts the process — a hand-edit to real code, a\n# scratch note, anything at all — because it's a SKETCH, not finished work:\n# there is no fork on which steering file the human happened to create;\n# `idle` has exactly one outgoing edge, into `unwind`, which reverts that\n# whole diff back out of the working tree before planning starts (its intent\n# survives in history for `design.triage` to read; the squash finale means it\n# never reaches the final commit either way). `unwind` itself has exactly one\n# outgoing edge, into `start-gate`.\n#\n# EVERY entry gates on a green baseline before proceeding, and all three now\n# keep the exact same plain rule: block on any red tree, full stop. A red run\n# at any of them halts at a human blocked gate (fix the failures and re-run,\n# exactly like the escalate states). `start-gate` needs no baseline-aware\n# second run any more — by the time it runs, `unwind` has already put the\n# tree back to the baseline, so ONE suite run there IS the baseline verdict.\n#\n# Once green, `design.triage` reads the diff that started the process (never\n# rendered into the prompt — it names the start commit and reads the range up\n# to the unwind commit itself) and groups it into an ORDERED list of\n# CONCERNS, each one leaving the suite green on its own, classifying each as\n# PRODUCT or TECHNICAL. It raises open questions for the product ones only,\n# and folds EVERYTHING the start diff added into the concerns — a scratch\n# sketch and a real hand-edit alike, since `unwind` already emptied the tree\n# of both; nothing is left to delete. `design.gate` is a shared check+answer\n# pair: its check probes `REQUIREMENTS.md` for unanswered questions and only\n# rests the process at a human answer gate when some remain — a\n# question-free phase skips the human stop entirely.\n#\n# Once the product questions are settled, `architecture.author` picks up as a\n# COLD reader (a separate machine, a separate memory scope — it reads\n# `REQUIREMENTS.md` in full rather than resuming design's conversation; COLD\n# means no memory of that conversation, never no git access — it still reads\n# the entry commit from history), develops the *how* for each settled\n# concern, and — now that it knows each concern's file footprint — may\n# re-merge concerns whose footprints coincide (never split), recording every\n# merge under its own `## Merged Concerns` heading and raising a TECHNICAL\n# open question only when it clears the same three-part bar triage does,\n# deciding every sub-threshold point itself — routed through the same shared\n# `questionGate` shape (`architecture.gate`) over `ARCHITECTURE.md`. Once\n# those are settled, `architecture.decompose` is a purely mechanical\n# write-out: one package file per concern, in the settled — possibly\n# coarsened — order, with no merge/split judgement of its own.\n#\n# From there the per-package build queue (`packages.*`) and the shared review\n# + squash tail (`build.*`) are UNCHANGED from before this restructure — see\n# their own machine doc comments below for the per-package build/review/\n# health mechanics.\n#\n# A second entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A third, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All entries converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which CAPTURES\n# the raw material (never interprets it) and routes on the step's shape —\n# THREE edges out of the review tail, not one:\n# * the SIGN-OFF edge: no comment at all (no REVIEW.md note, no code edit)\n# -> the diff is a bare REVIEW.md deletion ->\n# build.squashing -> done (the whole process collapses into one commit).\n# * the NON-ACTIONABLE edge: some comment exists, but `build.review.collecting`\n# judges it non-actionable (the human left only an approving remark, e.g. a\n# compliment with no code edit and no substantive note) -> CONSUMES the raw\n# capture (deletes it, the only change this turn) -> straight to\n# build.squashing too, WITHOUT spending a design lap producing nothing, and\n# with nothing left over to leak into the finale. The outcome is encoded by\n# which paths `collecting`'s own diff touches, never by a sentinel a file\n# pattern can't tell apart from real content — and never by a CLEAN turn\n# either, which stays the ordinary fruitless-dispatch attempt (no `C` row\n# here), not a verdict.\n# * the ACTIONABLE edge: `collecting` judges the round actionable (a note in\n# REVIEW.md, or a hand-edit to code) -> marks the raw capture (a real\n# edit) -> the root's own `re-unwind` state reverts the human's\n# review-round commit out of the working tree, scoped to real code (never\n# the review file, already gone, and never `.gtd/`) -> `design.triage`\n# re-derives the work from that history as intent only — the human's\n# hand-edit is a SKETCH to be re-planned, never built upon. This is a full\n# development lap (triage -> gate -> architecture -> gate -> packages ->\n# build.review again), not a quick fix-and-re-review; there is no\n# baseline check on the way back in (`re-unwind` is not an entry, so\n# `entryGate` never runs on this path).\n# One dead end never commits: the review-doc guard (src/StepGuards.ts) REFUSES\n# a deleted REVIEW.md — restore it, or leave a note (or edit code) to request\n# changes. A tick only records that a hunk was read; it never gates a land.\n#\n# The shared health pair (`build.health.check`/`.escalate`, plus its own\n# `build.fix`) no longer serves a feedback loop at all — with the implementer's\n# own coder-identity follow-through retired, the only edge into it is\n# `gtd --entry fix-precheck`'s red branch; they are not dead code, just\n# reachable from one place instead of two. The\n# per-package loop keeps its OWN health pair (`packages.item.health.check`/\n# `.escalate`, plus `packages.item.fix-suite`) because its green edge targets\n# the per-package `packages.item.spec.review` gate instead.\n#\nvars:\n testCommand: npm test\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot triage/design/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n # gtd's own voice for the files it generates — a specialisation of the\n # \"Spartan\" output style from alexgreensh/attention-span\n # (https://github.com/alexgreensh/attention-span), licensed AGPL-3.0,\n # written from a reading of that project's version 0.6. Rewritten in gtd's\n # own words for deliverables rather than chat replies; no upstream text\n # ships. A point-in-time derivation with no refresh mechanism — it will go\n # stale and nothing will notice.\n #\n # Keep/adapt/drop ledger — seventeen items reconciled: eight upstream rules\n # carried, five added, three dropped, rationale kept.\n #\n # Carried (six rules below cover eight upstream rules — two of them each\n # carry two upstream rules):\n # - the answer-first rule below carries both upstream's answer-first\n # rule and its cut-ruthlessly rule\n # - the never-trim rule below carries both upstream's\n # cut-elaboration-never-a-warning rule and its\n # numbers-and-thresholds-are-exact rule\n # - the deliverable rule carries upstream's answer-versus-deliverable\n # rule, minus its \"can't tell which? treat it as an answer\" tie-break —\n # gtd has no ambiguity to break, every injection site authors a file\n # - the blunt-and-imperative rule, the plain-words rule, and the bold\n # rule map one-to-one to upstream's equivalents\n #\n # Added and sharpened:\n # - five rules with no upstream equivalent: ship the artifact bare,\n # compressing is not dropping, one idea per block, flag risk in one\n # blunt line, never narrate the work\n # - two carried rules sharpened past upstream: the bold rule gains a\n # bold-only-reading test, and the plain-words rule gains a five-word\n # cap on defining an unavoidable term\n #\n # Dropped, each for its own reason:\n # - the arrow-point paragraph format: four of the seven voice-bearing\n # states are machine-parsed and their grammars require literal\n # `- [ ]` rows\n # - the go-deeper escape hatch: a state's own prompt is the request and\n # there is no channel to ask through\n # - the confirm-before-acting rule: every prompt is an instruction and\n # the actor's only output channel is the file\n styleBlock: |-\n This block opens with why density matters, not with rule one: the reader\n has a finite attention budget, and burying the line that mattered fails as\n hard as cutting it. This is motivation, not a rule — it commands nothing\n and never outranks never-trim.\n\n This file is a deliverable, not a chat reply. Its size follows the work —\n write what the task demands, and let padding, not length, be the target of\n any cut.\n\n Lead with the answer or the decision immediately. Skip the throat-clearing\n opener, and never circle back to restate the point once it has landed.\n\n Write in flat, commanding sentences — commit to the claim rather than\n hedging it.\n\n Favor everyday vocabulary over technical shorthand; where a term is\n unavoidable, define it in five words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it. The test: reading only the bold text yields the full point and every\n risk, or the bolding is wrong.\n\n Above every rule in this block: never trim a risk, a number, a threshold,\n or a scoped condition to save space. A claim that only holds under some\n condition ships with that condition. It outranks every other rule here.\n\n Ship the artifact bare. Output only what was asked for — no lead-in, no\n \"here's the…\", no sign-off wrapped around it. A generated file consumed\n as-is turns a wrapper into a defect.\n\n Compressing is not dropping. A task with three load-bearing parts ships all\n three, each shorter — reporting two of three findings has failed the brief,\n not fulfilled it.\n\n One idea per block; break when the idea shifts. Blocks may run any length,\n but one unbroken paragraph is a defect even when short.\n\n Flag risk and uncertainty in one blunt line. This is never-trim's\n constructive twin: that rule forbids deleting a caveat, this one forbids\n dissolving it into hedged prose.\n\n Never narrate the work — do it. Skip lines like \"I will now list the\n concerns\"; a file has no reason to describe its own composition.\n styleFormatContract: |-\n This file is machine-read. Its format contract outranks every style rule\n above it: keep every `##`/`###` heading this state specifies, every\n `- [ ]` checkbox row, and every required marker line exactly as specified.\n\n Do not replace a checkbox list with prose paragraphs. Do not renumber or\n rename a required heading. Do not drop a marker line. A parser reads these\n literally, and a violation refuses the turn outright — this is not a\n polite request, it is the one thing standing between this output and a\n failed parse.\n\n The voice rules above still govern the prose that sits between these\n structural elements. They do not govern the elements themselves.\n\n # The bundled default PERSONAS — one per prompt-bearing machine below. Each\n # is a machine's `system:` value (see src/StateFields.ts's `system` entry):\n # emitted verbatim as that machine's agent-CLI system prompt, stamped onto\n # every one of its own `prompt` states the same way `model:` is. Like every\n # other var, each is overridable via a top-level `.gtdrc` `vars:` key (e.g.\n # `vars: { builderPersona: \"...\" }`) or a `GTD_<NAME>`-style environment\n # variable — nothing below is privileged.\n #\n # `--system-prompt` REPLACES a harness's entire default system prompt; it\n # does not append to it, and it drops the harness's own injected cwd/env/\n # memory-path/git-status block. Each persona is therefore written as a\n # complete, self-contained system prompt: it states the role, gives tool-use\n # conduct (act, don't ask permission — this runs unattended), and tells the\n # agent to orient itself with real commands (there is no injected status\n # block) and to go inspect the commit range or path its turn's own prompt\n # names, rather than expect a diff pasted into the conversation.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. Nobody is watching this turn\n interactively: there is no one to grant permission, confirm a destructive\n command, or approve a plan before you act. Investigate with real commands\n (`git log`, `git show`, `ls`, `cat`) rather than assuming what a file or a\n commit contains, then act.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. Nothing has told you the\n current branch, the recent commit history, or whether the tree is clean —\n there is no injected status block. Run `git status`, `git log`, `ls` and\n similar yourself before you do anything else; that is the normal first\n move of this turn, not an extra step.\n\n The message you receive each turn is an instruction that names commits,\n paths, or file names for you to go inspect yourself — `git show <hash>`,\n `git diff`, reading a file directly — never a rendered diff pasted into\n the conversation. Expect to go look. Across the several turns this\n conversation may span, remember what you already read and settled in an\n earlier turn rather than re-deriving it from a cold read every time —\n unless the turn's own instructions tell you this is a fresh, cold pass.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one present to confirm a step or approve a draft before you commit to\n it. Investigate with real commands rather than assuming a file's contents\n or a decision's status, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block telling you the branch, recent history, or tree state — run\n `git status`, `git log`, `ls` and the like yourself before doing anything\n else.\n\n The message you receive each turn names files and, where relevant, commit\n ranges for you to go read yourself — never a diff or a plan rendered\n inline. Go look before you decide anything. When a later turn in this\n same conversation asks you to mechanically write out package files, work\n from the plan you already settled rather than re-deriving it, unless\n instructed to read it fresh.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended, so act\n on what you find rather than pausing for confirmation. Investigate with\n real commands — `git log`, `git show`, `git diff` — rather than assuming\n what changed.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block: no cwd banner, no environment summary, no memory-file list,\n no git-status readout. Run `git status`, `git log`, `ls` yourself first.\n\n The message you receive each turn names a commit range or a file for you\n to inspect yourself — `git show`, `git diff` against the range it gives\n you — not a diff pasted into the conversation. Go read the actual history\n before you write or judge anything; the range is often wider than the\n single round in front of you, so check what it actually spans rather than\n assuming it matches your last turn.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. Nobody is present to confirm a\n verdict interactively, so investigate with real commands and commit to\n what you find rather than hedging or asking for permission to look.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment summary, no git status handed to\n you. Orient yourself with `git status`, `git log`, `ls` and the like\n before judging anything.\n\n The message you receive each turn names a spec file to read and a commit\n range for you to inspect yourself — `git show`, `git diff` — never a\n diff rendered inline, and the range is often wider than the one package\n you are checking, since it can span everything built so far in this\n process. Go look before you approve or object; do not assume the range\n matches only the most recent change.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you — never wander into another package's files\n or tasks just because you can see them, and never refactor code the\n current task doesn't touch. When fixing, treat the feedback you're given\n as the diagnosis to act on, not a suggestion to second-guess without\n evidence — but if it is simply wrong, say so by discarding it rather than\n forcing a change onto code that doesn't need one.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to approve a plan or confirm a command before you run it. Write\n the failing test, watch it fail, then make it pass — investigate the\n actual current state of the code and the suite with real commands rather\n than assuming either.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment info, no memory-file paths, no git\n status. Run `git status`, `git log`, `ls` yourself before you start\n editing anything.\n\n The message you receive each turn names the package spec, the feedback\n file, or the check output for you to read yourself at its actual path —\n never a diff or a rendered summary handed to you inline. Go read the\n file, and where a commit range is named, go inspect it with `git show` or\n `git diff` rather than expecting the change pasted in front of you.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before everything it touched is\n squashed into a single commit. Most turns you are handed, your job is to\n write the one conventional-commit message that summarizes the whole\n approved change: subject line, imperative mood, a body that explains why\n and what trade-offs were made, not just what changed. Occasionally you\n are handed a late-breaking failing check instead — fix it, focused and\n minimal, before the process can close. Either way, you are aware that\n everything you touch this turn is about to lose its own commit boundary\n and become part of one final message, so make that message earn its\n place as the historical record.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended, so act\n on what you find rather than waiting for confirmation. Investigate the\n actual commit range and the actual failing output with real commands\n rather than assuming either.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment summary, no git status. Run\n `git status`, `git log`, `ls` yourself before acting.\n\n The message you receive each turn names a commit range or a feedback file\n for you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff rendered inline into the conversation. Go look\n before you write the commit message or the fix; if you already built the\n range earlier in this same conversation, work from what you already know\n rather than re-deriving it.\n\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts — both `start-gate` and\n # `review-gate` reach this one check, and its script IS the shared\n # `&suiteCheck` anchor (defined once, right here, and aliased by\n # `fix-precheck` and `healthGate.check` below); the `on` rules turn the\n # result into \"halt at blocked\" vs \"proceed to $onGreen\".\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\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 # Sweep the raw review capture — no path from `deciding`/`collecting`\n # reaches this check; this is a leftover sweeper for a capture an\n # earlier, abandoned process may have left committed here (see the\n # `D .gtd/REVIEW_RAW.md` rows below). A no-op when it is already gone\n # or never existed.\n rm -f .gtd/REVIEW_RAW.md\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 .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # `entry: true` on the shared local marks both `entryGate` instances\n # (start-gate/review-gate) as manual entries — only `review-gate.check`\n # actually needs the reachability root (it is otherwise unreachable\n # from `entries.default`), but the shared dedup makes marking one mark\n # both. Accepted as correct: both ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `start-gate` binds the literal\n # empty string, which compiles away to \"no base\" here — it is reached\n # from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n # `file:` is the one field with an implicit prefix: the compiler\n # resolves it under `.gtd/` automatically, so `file: FEEDBACK.md` here\n # sits beside `on: \"A .gtd/FEEDBACK.md\"` above on purpose — every OTHER\n # path in this template (an `on` pattern, a script body, a prompt/\n # message) still spells `.gtd/` in full.\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records `.gtd/FEEDBACK.md` and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the build tail's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── DEDUP: the open-questions check/answer pair (identity-free) ───────────\n # Shared by design and architecture's own gate (`design.gate`/\n # `architecture.gate`): `check`'s script probes whichever of\n # REQUIREMENTS.md/ARCHITECTURE.md currently exists (see the script's own\n # comment) with `gtd check qa --open-questions` and stamps/removes\n # `QUESTIONS.md` on the result; only when it's present does the process\n # rest at `answer`, a human gate. A question-free phase never rests here at\n # all — `check`'s own `on` rows skip straight to `$onNone`. Takes NO `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path cannot be spliced into a shared script body —\n # `file` is instead the PARAM `answer` renders as its own `file:` (the\n # human-facing gate), a different use entirely from the probe.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # gtd check turn — probe the current phase's steering file for\n # unanswered questions; an answered/empty result removes the marker\n # (.gtd/QUESTIONS.md), an unanswered one stamps it fresh. Design and\n # architecture share this one probe: whichever of\n # .gtd/REQUIREMENTS.md/.gtd/ARCHITECTURE.md exists ON DISK right now\n # IS this phase's steering file — unambiguous because\n # architecture.author deletes .gtd/REQUIREMENTS.md in the same turn\n # it writes .gtd/ARCHITECTURE.md, so exactly one of the two exists\n # here.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # ── ENCAPSULATION: the triage phase's one conversation ─────────────────────\n # Groups the diff that started the process into ordered, classified\n # concerns and resolves the PRODUCT ones' open questions that clear the\n # bar — deciding every sub-threshold point itself instead — one machine\n # identity across the whole back-and-forth, so the agent explores the\n # codebase once and the human's answer rationale survives every return lap.\n # `triage`'s prompt reads THREE laps off the requirements file's own\n # content, never off which edge landed here (the resting state is the same\n # either way): FIRST (the file is absent), RETURN (a ticked/answered\n # question waiting to be folded), and REVIEW LOOP-BACK (concerns already\n # present, nothing ticked-but-unfolded — `re-unwind` put them there, not an\n # answer). The loop-back lap also runs the suite itself and files any\n # breakage it finds as its own FIRST concern: `re-unwind` sits between\n # `build.review.deciding` and here with no `entryGate` in between, so a red\n # the revert causes is otherwise ambient. Left for\n # `packages.item.health.check` to trip over instead, it would spend one of\n # `packages.item.fix-suite`'s retry cap — process-pooled across every\n # package, the same pattern `specReview.review`'s own retry comment\n # documents — on breakage the package itself never caused.\n # ▸ planner identity — `model:` declared once here, stamped onto `triage`\n # (its only prompt state; `gate.answer` is a human turn and carries no\n # prompt).\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.designPersona %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # An agent that deletes the assembled review input without folding it\n # in is the original \"captured then discarded\" bug, now against THIS\n # state's own file — the retired `build.addressing` state was the\n # guard's only user before this restructure; this is its new one.\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the HUMAN'S INPUT — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n On the FIRST lap, `.gtd/REQUIREMENTS.md` does not exist\n yet (or holds whatever the human's own change happened to leave\n there). Build it from that start diff:\n\n 1. Group the diff into an ORDERED list of CONCERNS — each one a\n coherent piece of work that can leave the test suite GREEN on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n 2. Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n 3. For every concern's open point, PRODUCT or TECHNICAL, a question\n is warranted only when all three hold: the answer is a genuine\n fork with divergent outcomes — user-visible for a product\n question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already\n settle it (treat anything the entry diff states explicitly — a\n directive in `.gtd/TODO.md`, a committed choice in a hand-edit —\n as already settled, and on a review loop-back lap treat\n whatever the human's own review-round edit or note states\n explicitly the same way); and getting it wrong would survive\n all the way to the human review tail.\n\n Anything below that bar, PRODUCT or TECHNICAL alike, decide\n yourself: record the decision under `## Answered Questions` —\n the same section a human-answered question lands in — as\n `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a\n `## Open Questions` heading near the top and takes this shape:\n one `### <question>` sub-heading each, followed by a checkbox\n list of candidate answers — TWO concrete options plus a final\n free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Raise questions ONLY for product concerns here — a technical\n concern's open points wait for the next phase. Never tick a box\n yourself — the human ticks exactly one per question.\n\n When every concern in this lap is TECHNICAL, write no\n `## Open Questions` section at all.\n 4. Fold EVERYTHING the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a SKETCH of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n On a RETURN lap the human has ticked one option per question (or\n replaced `_your answer_` with their own text) in\n `.gtd/REQUIREMENTS.md`. For each answered question: fold\n the chosen answer into its concern's prose, then MOVE the question\n down into a `## Answered Questions` section as `### <question>`\n followed by the resolved answer in plain PROSE (drop the\n checkboxes). Never re-raise a question the human deleted; if the\n human deleted the whole `## Open Questions` section, treat every\n product concern as accepted and surface no new questions unless\n something is genuinely still blocking. Omit `## Open Questions`\n entirely once there are none left.\n\n On a REVIEW LOOP-BACK lap, `.gtd/REQUIREMENTS.md`\n already holds concerns and none of them have a ticked-but-unfolded\n answer waiting — that shape means a completed REVIEW ROUND put\n this material here, not a question you asked. Develop those\n existing concerns further with whatever the round raised; never\n rediscover or regroup them from a cold read the way the FIRST lap\n does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On a FIRST lap that\n same hash IS the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on a first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be RED —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the FIRST concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n\n Leave `.gtd/REQUIREMENTS.md` uncommitted and finish your\n turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # ── ENCAPSULATION: the technical phase's one conversation ──────────────────\n # A separate machine from `designPlan` — its own memory scope, since there\n # may be a handover between the user-facing and the technical phase: this\n # phase does NOT resume design's conversation, it reads\n # `.gtd/REQUIREMENTS.md` cold — COLD means no memory of that conversation,\n # never no git access; it still finds and reads the entry commit from\n # history the same way triage does. Resolves the TECHNICAL open questions\n # that clear the same bar triage's own do, deciding every sub-threshold\n # point itself instead, then decomposes the settled concerns into package\n # files. Merge authority lives HERE, in `author` — it is the first state\n # that knows the *how*, so it alone may re-merge concerns whose file\n # footprints coincide (never split); `decompose` remains the mechanical\n # write-out, carrying `author`'s settled — possibly coarsened — grouping\n # over verbatim. ▸ planner identity — `model:` declared once here, stamped\n # onto `author` and `decompose` (its own prompt states; `gate.answer` is a\n # human turn and carries no prompt).\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.architectPersona %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do NOT resume the design conversation — this is a separate\n machine with its own memory, a COLD read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as SETTLED; never re-open it.\n\n COLD means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n On the FIRST lap, develop `.gtd/ARCHITECTURE.md` from\n those concerns: for each one, in the same order, work out the *how*\n — file/module structure, data models, library/tech-stack choices,\n error-handling strategy — building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n For every open TECHNICAL point, a question is warranted\n only when all three hold: the answer is a genuine fork with\n divergent outcomes — user-visible for a product question,\n materially different implementations for a technical one; the\n sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive\n in `.gtd/TODO.md`, a committed choice in a hand-edit — as\n already settled, and on a review loop-back lap treat whatever\n the human's own review-round edit or note states explicitly the\n same way); and getting it wrong would survive all the way to\n the human review tail.\n\n Anything below that bar, decide yourself: record the decision\n under `## Answered Questions` — the same section a\n human-answered question lands in — as\n `### <the point phrased as a question>` followed by the decision\n and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a\n `## Open Questions` heading, exactly as the requirements phase\n does: one `### <question>` each, followed by a checkbox list —\n TWO concrete options plus a final free-text slot, ALL left\n UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one\n option per question (or replaced `_your answer_` with their own\n text): fold each chosen answer into the plan prose, then MOVE the\n question into a `## Answered Questions` section as `### <question>`\n + the resolved answer in plain PROSE (drop the checkboxes). Never\n re-raise a deleted question; treat a deleted `## Open Questions`\n section as acceptance. Omit `## Open Questions` once there are none\n left.\n\n Once `.gtd/ARCHITECTURE.md` is written, delete\n `.gtd/REQUIREMENTS.md` — its content has been folded in\n and it must not linger. Leave everything uncommitted and finish\n your turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ORDERED set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do NOT merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). `collecting` JUDGES whether a\n # round is actionable and, when it is, CLASSIFIES it straight into\n # `REQUIREMENTS.md` as ordered, PRODUCT/TECHNICAL concerns — the same\n # artifact `design.triage` develops — never an instruction list, and never a\n # build. An actionable round still leaves this machine entirely (the root's\n # own `re-unwind`, then `design.triage` folds the concerns in cold), so\n # there is no coder-identity follow-through inside this machine any more.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.reviewerPersona %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). The pointer's OWN line carries nothing but the\n pointer — no note, no dash. Its explanation goes on the line(s)\n beneath it instead:\n\n - [ ] ./path/to/file.ts#42\n what this hunk does, with room to run to several lines\n\n Text after the pointer token on the pointer's own line is a\n validation error, not just a style nit — an older-format doc like\n `- [ ] ./path/to/file.ts#42 — note` is UNLANDABLE.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\" — it never gates a land, whatever the\n # boxes say. The one dead end that CAN'T be told apart by a file-pattern\n # edge is a deleted REVIEW.md, so the review-doc guard\n # (`src/StepGuards.ts`) catches it BEFORE the step commits and refuses.\n reviewWindow: true\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to collapse the process into\n one commit (**build.squashing**), whatever the boxes say.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (`await-review`'s single\n # `\"* **\": deciding` edge means EVERY human step there lands here, so\n # `deciding` is entered exactly once per round — nothing else commits in\n # between). It is FEEDBACK when the human left\n # a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\" tick) OR a\n # hand-edit to any file this round outside `.gtd/` (every gtd steering\n # file, including REVIEW.md itself, lives there). Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into\n # REVIEW_RAW.md (the `collecting` agent then judges whether it's\n # actionable); it does NOT interpret it. Either way, remove REVIEW.md;\n # what the resulting diff MEANS is decided by this state's own `on`\n # rules — the A/M REVIEW_RAW.md rows are declared BEFORE the D\n # REVIEW.md row so a feedback round (which also deletes REVIEW.md) is\n # captured as feedback, not mistaken for a sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides .gtd/?\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n fi\n rm -f .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n\n # Judges actionability and, when actionable, classifies straight into\n # `REQUIREMENTS.md` — an ordered, PRODUCT/TECHNICAL concern list, never\n # an instruction list for a builder (the actionable path re-plans from\n # history via `re-unwind` + `design.triage`; it never builds on the\n # human's own edit). The outcome is encoded by which paths ITS OWN diff\n # touches, never by a sentinel: writing `REQUIREMENTS.md` (`A`/`M`) IS\n # the actionable case — row ORDER matters, since an actionable round's\n # diff is `A REQUIREMENTS.md` + `D REVIEW_RAW.md` together, so the\n # requirements rows must be declared first or every round would\n # short-circuit to sign-off. CONSUMING `REVIEW_RAW.md` alone (deleting\n # it, the only change this turn) is the non-actionable short-circuit —\n # the same trick `deciding` already uses, one state earlier; it reuses\n # `$onSignoff` (already bound to `build.squashing`) rather than a new\n # param, because the short-circuit IS a sign-off. Declaring no `C` row\n # and no `* **` row is deliberate: a clean turn here is a fruitless\n # dispatch (a crash, a refusal, a context-limit cutoff), never a\n # verdict, so it falls through to the ordinary `prompt`-state default\n # instead — an empty ATTEMPT commit, remembered as a stall\n # (`Edge.ts`'s `stalledAt`) rather than silently misread as \"the human's\n # feedback was non-actionable\" — and a dirty tree matching neither row\n # is a REFUSAL (you classify, you do not build). `REQUIREMENTS.md`\n # survives `re-unwind` (it lives under `.gtd/`, excluded from its own\n # revert pathspec) for `design.triage` to fold in cold; a consumed\n # `REVIEW_RAW.md` with no requirements written is gone before\n # `build.squashing` ever runs, so nothing leaks into the finale.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do NOT change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is ACTIONABLE if any of the following is true:\n\n 1. The human left a NOTE on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a MANDATORY concern below, whether or not\n its box is ticked.\n 2. The human ADDED a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an INSTRUCTION: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a SKETCH, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is NOT actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability to justify a lap, and never dismiss a real note\n or edit as approval.\n\n If the round is ACTIONABLE: write `.gtd/REQUIREMENTS.md`\n with an ORDERED list of CONCERNS, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise NO open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is NOT actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change IS the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # ── ENCAPSULATION: the per-package agentic review ──────────────────────────\n # A single state: a machine whose entire job is to be a separate MIND from\n # the implementer, distinct from the coder identity that builds/fixes the\n # package (`packages.item.*`). ▸ planner identity — `model:` declared once\n # here, stamped onto `review` (its only prompt state). Fixing the reviewer's\n # own findings is a coder action, not a reviewer one — it lives in\n # `packages.item.fix-spec` (`$onFix`), not inside this machine.\n specReview:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.specReviewerPersona %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # Bounded per process: once entered `max` times, a transition into it is\n # redirected to `$onApproved` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `build.fix` cap has (see the README's \"Why\" section, \"Bounded, not\n # runaway\").\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the per-package build queue ─────────────────────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\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 mkdir -p .gtd\n # Sweep the design/architecture phases' spent steering files:\n # REQUIREMENTS.md is consumed by architecture.author, ARCHITECTURE.md\n # by architecture.decompose, and QUESTIONS.md by whichever gate ran\n # last — all three are gone by the time the package queue starts.\n # REVIEW_RAW.md is swept here too: the review loop-back\n # (re-unwind) excludes .gtd from its revert, so an actionable\n # feedback round's MARKED raw capture (see humanReview's own\n # `collecting` state) rides through design and architecture\n # untouched — this is the ONLY sweeper on that path\n # before the queue (and eventually the squash finale) would\n # otherwise pick it up as stray content. No-op if already removed.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Package file names are gtd-authored (architecture.decompose writes\n # NN-name.md), never containing whitespace/newlines that would break\n # ls | head.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the per-package build identity ──────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n system: <%~ it.vars.builderPersona %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (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 `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the process's build identity ─────────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `squashing`\n # and `fix` (its own prompt states). There is no monolithic \"build the whole\n # plan\" state any more — every package is built inside `packages.item`, and\n # the queue drains straight into `build.review`. There is no coder-identity\n # follow-through on review feedback here any more — an actionable round\n # leaves this machine entirely, via `$onFeedback` bound at the root's own\n # `build:` reference to the root-local `re-unwind` state (see the header\n # comment's \"Why humanReview is nested\" section for why the review tail\n # still stays nested here even so). `humanReview` is nested HERE (not at the\n # root) so `build.squashing` — the one coder turn a sign-off still resumes —\n # stays inside the builder's own scope.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: <%~ it.vars.finisherPersona %>\n params: [onDone, onFeedback]\n # Nothing resolves this machine BARE (every reference into it names a\n # specific descendant, e.g. `build.review` / `build.fix`) — but a machine\n # must still declare an `entry:` local per its own grammar, so this names\n # an arbitrary one of its own states.\n entry: fix\n states:\n squashing:\n actor: agent\n label: Squashing\n file: COMMIT_MSG.md\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent. The process is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `.gtd/COMMIT_MSG.md`; write\n it. Do not create other files to hold notes or output.\n\n Leave `.gtd/COMMIT_MSG.md` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `.gtd/COMMIT_MSG.md` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `.gtd/COMMIT_MSG.md` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A .gtd/COMMIT_MSG.md\": $onDone\n \"M .gtd/COMMIT_MSG.md\": $onDone\n\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (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 `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so `squashing`\n # stays inside the builder's own scope on sign-off — see the header\n # comment. Sign-off -> squashing; feedback -> $onFeedback, bound at the\n # root's own `build:` reference to `re-unwind` (a root-local, not a\n # descendant of this machine — the review tail's own scope ends here on\n # the actionable path).\n review:\n machine: humanReview\n with:\n onSignoff: squashing\n onFeedback: $onFeedback\n\n # The root machine — the whole one-flow tree described in this file's\n # header comment. WIRING ONLY: no identity of its own, just the shared\n # entry/exit states and the top-level machine tree.\n unified:\n entry: idle\n states:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n label: Idle\n file: TODO.md\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # ── Green-baseline gate: `gtd --entry fix-precheck` entry ─────────────────\n # `gtd --entry fix-precheck` enters HERE: run the suite, then a red run\n # drops straight into the shared `build.fix` loop (-> build.health.check ->\n # review + squash tail); a green run is a no-op back to `idle` (nothing\n # to fix). Aliases the `&suiteCheck` anchor defined on `entryGate.check`'s\n # own `script:` above — the same plain suite check `start-gate`/\n # `review-gate` run.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(\".gtd/COMMIT_MSG.md\") %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Unwind: revert the entry commit's diff before planning starts ──────\n # Kept a SEPARATE state rather than folded into `start-gate.check`'s own\n # script: the `blocked -> check` retry edge re-runs the gate, so a\n # revert living there would discard the human's own baseline repair and\n # would need an idempotence guard. With exactly one inbound edge (from\n # `idle`), `unwind` is structurally reached once per process and needs\n # no guard at all. Names the reverted commit via `it.currentCommit`\n # (they're the same commit here), NOT a bare `HEAD` — pinning the\n # render-time hash keeps the script correct even if a driver runs it\n # late.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n # gtd check turn — revert the entry commit's diff out of the working\n # tree. Its content survives in history for design.triage to read.\n set +e\n # Hoist the commit hash once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last\n # token on a line.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # ── Re-unwind: revert an actionable review round's hand-edit before ────\n # re-planning starts. A SECOND unwind-shaped state, not a second inbound\n # edge on `unwind` above: `unwind`'s own doc comment turns on it having\n # exactly one inbound edge (from `idle`), which is why it carries no\n # idempotence guard — adding a second caller there would break that\n # invariant. Names the reverted commit via `it.reviewBase`, never\n # `it.currentCommit` (by now that names the assembly turn's own commit)\n # and never a bare `HEAD`: `reviewBaseFor` walks the process trace\n # backwards for the last commit that ENTERED a `reviewBase: true` state\n # — `build.review.deciding` is the template's only one — which is\n # exactly the human's own `await-review -> deciding` commit, the same\n # hash the next review round's incremental base already uses.\n #\n # SCOPED, not wholesale: `deciding` already `rm -f`'d the review file,\n # so a plain `git revert --no-commit` would resurrect it (and conflict\n # on it). Instead this renders that ONE commit's diff with `.gtd/`\n # excluded from the pathspec (REVIEW.md lives there too) and\n # reverse-applies it. The apply is EXPECTED to succeed —\n # nothing between that commit and now touches a code path — but it can\n # still FAIL: a clean-filter round-trip, a drifted worktree, or a binary\n # path can all make `git apply -R` a no-op on an otherwise-clean tree,\n # indistinguishable from a legitimate note-only round. `requireRevert`\n # (below) is what catches that: its step-capture guard\n # (`src/StepGuards.ts`) re-establishes the fact from the tree itself —\n # comparing the human's own commit's paths against the review base's\n # parent — and refuses the step while residue remains, rather than\n # trusting the script's swallowed exit code. A note-only round's patch\n # is EMPTY, and `git apply` on empty input FAILS (\"No valid patches in\n # input\"), hence the `-s` guard rather than `--allow-empty` (which would\n # impose git >= 2.35). The apply is worktree-only, no `--index`: the\n # emitted step script's own `git add -A && git commit` stages it, and\n # `changedPaths` answers by CONTENT, so an unstaged revert is still the\n # pending diff the pattern matches — one fewer index write in a\n # worktree gtd shares with the editor's SCM. The patch file lives under\n # `.gtd/` (excluded from its own pathspec, the same precedent as the\n # check scripts' own `.check-output`) and is removed before the step\n # lands.\n #\n # Both `on` rows target the SAME place (`design`) because either shape\n # is a legitimate outcome here, never a stall: a hand-edited round's\n # patch is non-empty, so the revert leaves a dirty tree (the `\"* **\"`\n # row); a note-only round's patch is empty, so nothing to apply leaves\n # a CLEAN tree — and a clean tree at a `script` state with no `C` row\n # would otherwise be a silent no-op that never advances. Declaring `C`\n # here makes that the expected, terminating case instead.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist ONLY\n # to give the require-revert guard the path it must exempt from residue\n # (the engine blesses no variable names, so the guard reads `Rest.hints.\n # file`, never this literal itself) — the review file is already\n # gone two commits earlier, so this state never renders it. The accepted\n # presentational cost: `gtd next --json`/`gtd land --json` carry that\n # already-deleted path as this state's `file:` hint, and the visualizer\n # labels `re-unwind` with it too. Bounded — `Lsp.ts`'s `buildSteeringMap`\n # skips a mode-less state, so `re-unwind` contributes no path→mode entry\n # and raises no \"already mapped by an earlier state\" warning against\n # `await-review`/`deciding`, which declare the same rendered path with\n # `mode: review`.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # gtd check turn — revert the human's own review edit out of the\n # working tree, scoped to real code: everything under `.gtd/` is\n # excluded, because the review file is already gone by this point\n # and reverting it would resurrect it. The human's intent survives\n # in their own commit for the triage phase to read. The apply is\n # EXPECTED to succeed — nothing between that commit and now touches\n # a code path — but it can still fail on a clean-filter round-trip,\n # a drifted worktree, or a binary path: the `requireRevert`\n # step-capture guard (src/StepGuards.ts) catches that by\n # re-checking the tree itself before this step is allowed to land.\n # The guard's own residue scoping (`isCodePath`) re-derives the same\n # `.gtd/` exemption this pathspec renders — keep the two together\n # when either changes.\n set +e\n # Hoist the reverted commit once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token\n # on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n # ── Green-baseline gate: the single start entry. ───────────────────────\n # Runs on the tree AFTER `unwind`, so this is now just the ordinary\n # green-baseline check `review-gate`/`fix-precheck` also run — no\n # baseline-aware second run, no detached checkout, no HEAD move\n # anywhere in gtd's emitted scripts any more.\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── The triage phase (design.triage -> design.gate.check/.answer -> architecture). ──\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n # ── The technical phase (architecture.author -> architecture.gate.check/.answer ─\n # -> architecture.decompose -> packages). ──────────────────────────────\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n # ── The per-package build queue (packages.* -> build.review). ─────────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── The process's build identity (build.* -> build.review -> done). ───────\n build:\n machine: buildTail\n with:\n onDone: done\n onFeedback: re-unwind\n";
192580
+ var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceMachineFields`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`).\n#\n# * entryGate (dedup ×2) — the green-baseline gate (check + human blocked)\n# guarding the single start (`start-gate`) and\n# the review (`review-gate`) entries. Its check\n# script IS the shared `&suiteCheck` anchor\n# (also aliased by `fix-precheck`, outside this\n# machine) — every entry blocks on any red tree,\n# full stop; there is no baseline-aware second\n# run any more, since `unwind` (below) already\n# put the tree back to the baseline before this\n# gate ever runs. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# build tail's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * questionGate (dedup ×2) — the check/answer pair shared by the design and\n# architecture phases' own open-questions gate\n# (`design.gate.*` / `architecture.gate.*`): a\n# shared probe script decides whether the\n# phase's steering file still has unanswered\n# questions, and only then rests the process at\n# a human answer gate. Identity-free.\n# * designPlan (encap) — the triage phase's one conversation: group the\n# diff that started the process into ordered,\n# classified concerns and resolve the PRODUCT\n# ones' open questions that clear the bar — a\n# genuine fork, not already settled, worth\n# surviving to review — deciding every\n# sub-threshold point itself instead\n# (`design.*`: triage, gate.check,\n# gate.answer). ▸ planner.\n# * archPlan (encap) — the technical phase's one conversation: a COLD\n# read of the settled concerns, resolving the\n# TECHNICAL open questions that clear the same\n# bar (same decide-it-yourself sink below it),\n# then a mechanical decomposition into package\n# files (`architecture.*`: author, gate.check,\n# gate.answer, decompose). A separate machine\n# from `designPlan` (not one fused machine) —\n# its own memory scope, since there may be a\n# handover between the user-facing and the\n# technical phase. ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `buildTail` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — `collecting` only judges\n# whether the round is actionable; there is no\n# coder-identity follow-through inside this\n# machine any more (see the root's own\n# `re-unwind` state).\n# * specReview (encap) — the per-package agentic review, a SINGLE\n# state (`packages.item.spec.review`) — a\n# machine whose entire job is to be a separate\n# mind from the implementer. ▸ planner — fixing\n# the reviewer's own findings is a coder action,\n# so it lives in `packages.item.fix-spec`\n# instead.\n# * packageLoop (encap) — the per-package build queue\n# (`packages.picking`). Identity-free — the\n# actual per-package build identity lives in its\n# own `item` child (packageItem, below).\n# * packageItem (encap) — the per-package build identity (`packages.\n# item.*`: building, fix-suite, fix-spec,\n# closing), plus its own `health` and `spec`\n# children. ▸ coder.\n# * buildTail (encap) — the process's build identity (`build.*`:\n# squashing, fix), plus its own `health` child\n# AND its own `review` child\n# (humanReview, above). No monolithic \"build the\n# whole plan\" state any more — every package is\n# built inside `packages.item`, and the queue\n# drains straight into `build.review`. ▸ coder.\n#\n# ── Why humanReview is nested, not a root sibling ───────────────────────────\n#\n# `humanReview` sits UNDER `buildTail` (`build.review`) rather than beside it\n# at the root, because a machine's memory scope is its dotted instance path\n# (see src/PatternMachine.ts's `memoryScopeAt`): entering a DESCENDANT scope\n# never breaks the parent's unbroken run, but entering a sibling does. On the\n# ORDINARY path (packages -> build.review -> squashing) that buys nothing —\n# `build`'s own scope has no prior turn to resume, since every package is\n# built inside `packages.item` instead. The one concrete case where it\n# matters is `gtd --entry fix-precheck`: a red suite drops into `build.fix`,\n# which opens `build`'s own scope, and the whole run from there —\n# `build.fix` -> `build.health.check` -> `build.review.*` -> `build.squashing`\n# — stays inside that one subtree, so `build.squashing` drafts the finale\n# from the SAME session that made the fixes, instead of a root-level sibling\n# forcing it to re-derive the range from scratch on every pass through the\n# tail. That one benefit is worth keeping over hoisting `humanReview` back to\n# a root sibling, which would otherwise be a repo-wide rename with no\n# behavioural gain. (The OTHER coder turn a feedback round used to resume —\n# the implementer's own follow-through on review feedback — is retired\n# outright: a hand-edit made during review is now re-planned from scratch via\n# the root's own `re-unwind` state, never built upon, so there is no\n# continuity to preserve on that path any more.)\n# The reviewer itself is unaffected either way: `build.review.*` is its own\n# scope regardless of where it's nested, so it never resumes the builder's\n# session.\n#\n# ── One flow: triage into concerns, then packages, then the shared tail ────\n#\n# ANY change to the tree starts the process — a hand-edit to real code, a\n# scratch note, anything at all — because it's a SKETCH, not finished work:\n# there is no fork on which steering file the human happened to create;\n# `idle` has exactly one outgoing edge, into `unwind`, which reverts that\n# whole diff back out of the working tree before planning starts (its intent\n# survives in history for `design.triage` to read; the squash finale means it\n# never reaches the final commit either way). `unwind` itself has exactly one\n# outgoing edge, into `start-gate`.\n#\n# EVERY entry gates on a green baseline before proceeding, and all three now\n# keep the exact same plain rule: block on any red tree, full stop. A red run\n# at any of them halts at a human blocked gate (fix the failures and re-run,\n# exactly like the escalate states). `start-gate` needs no baseline-aware\n# second run any more — by the time it runs, `unwind` has already put the\n# tree back to the baseline, so ONE suite run there IS the baseline verdict.\n#\n# Once green, `design.triage` reads the diff that started the process (never\n# rendered into the prompt — it names the start commit and reads the range up\n# to the unwind commit itself) and groups it into an ORDERED list of\n# CONCERNS, each one leaving the suite green on its own, classifying each as\n# PRODUCT or TECHNICAL. It raises open questions for the product ones only,\n# and folds EVERYTHING the start diff added into the concerns — a scratch\n# sketch and a real hand-edit alike, since `unwind` already emptied the tree\n# of both; nothing is left to delete. `design.gate` is a shared check+answer\n# pair: its check probes `REQUIREMENTS.md` for unanswered questions and only\n# rests the process at a human answer gate when some remain — a\n# question-free phase skips the human stop entirely.\n#\n# Once the product questions are settled, `architecture.author` picks up as a\n# COLD reader (a separate machine, a separate memory scope — it reads\n# `REQUIREMENTS.md` in full rather than resuming design's conversation; COLD\n# means no memory of that conversation, never no git access — it still reads\n# the entry commit from history), develops the *how* for each settled\n# concern, and — now that it knows each concern's file footprint — may\n# re-merge concerns whose footprints coincide (never split), recording every\n# merge under its own `## Merged Concerns` heading and raising a TECHNICAL\n# open question only when it clears the same three-part bar triage does,\n# deciding every sub-threshold point itself — routed through the same shared\n# `questionGate` shape (`architecture.gate`) over `ARCHITECTURE.md`. Once\n# those are settled, `architecture.decompose` is a purely mechanical\n# write-out: one package file per concern, in the settled — possibly\n# coarsened — order, with no merge/split judgement of its own.\n#\n# From there the per-package build queue (`packages.*`) and the shared review\n# + squash tail (`build.*`) are UNCHANGED from before this restructure — see\n# their own machine doc comments below for the per-package build/review/\n# health mechanics.\n#\n# A second entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A third, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All entries converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which CAPTURES\n# the raw material (never interprets it) and routes on the step's shape —\n# THREE edges out of the review tail, not one:\n# * the SIGN-OFF edge: no comment at all (no REVIEW.md note, no code edit)\n# -> the diff is a bare REVIEW.md deletion ->\n# build.squashing -> done (the whole process collapses into one commit).\n# * the NON-ACTIONABLE edge: some comment exists, but `build.review.collecting`\n# judges it non-actionable (the human left only an approving remark, e.g. a\n# compliment with no code edit and no substantive note) -> CONSUMES the raw\n# capture (deletes it, the only change this turn) -> straight to\n# build.squashing too, WITHOUT spending a design lap producing nothing, and\n# with nothing left over to leak into the finale. The outcome is encoded by\n# which paths `collecting`'s own diff touches, never by a sentinel a file\n# pattern can't tell apart from real content — and never by a CLEAN turn\n# either, which stays the ordinary fruitless-dispatch attempt (no `C` row\n# here), not a verdict.\n# * the ACTIONABLE edge: `collecting` judges the round actionable (a note in\n# REVIEW.md, or a hand-edit to code) -> marks the raw capture (a real\n# edit) -> the root's own `re-unwind` state reverts the human's\n# review-round commit out of the working tree, scoped to real code (never\n# the review file, already gone, and never `.gtd/`) -> `design.triage`\n# re-derives the work from that history as intent only — the human's\n# hand-edit is a SKETCH to be re-planned, never built upon. This is a full\n# development lap (triage -> gate -> architecture -> gate -> packages ->\n# build.review again), not a quick fix-and-re-review; there is no\n# baseline check on the way back in (`re-unwind` is not an entry, so\n# `entryGate` never runs on this path).\n# One dead end never commits: the review-doc guard (src/StepGuards.ts) REFUSES\n# a deleted REVIEW.md — restore it, or leave a note (or edit code) to request\n# changes. A tick only records that a hunk was read; it never gates a land.\n#\n# The shared health pair (`build.health.check`/`.escalate`, plus its own\n# `build.fix`) no longer serves a feedback loop at all — with the implementer's\n# own coder-identity follow-through retired, the only edge into it is\n# `gtd --entry fix-precheck`'s red branch; they are not dead code, just\n# reachable from one place instead of two. The\n# per-package loop keeps its OWN health pair (`packages.item.health.check`/\n# `.escalate`, plus `packages.item.fix-suite`) because its green edge targets\n# the per-package `packages.item.spec.review` gate instead.\n#\nvars:\n testCommand: npm test\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot triage/design/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n # gtd's own voice for the files it generates — a specialisation of the\n # \"Spartan\" output style from alexgreensh/attention-span\n # (https://github.com/alexgreensh/attention-span), licensed AGPL-3.0,\n # written from a reading of that project's version 0.6. Rewritten in gtd's\n # own words for deliverables rather than chat replies; no upstream text\n # ships. A point-in-time derivation with no refresh mechanism — it will go\n # stale and nothing will notice.\n #\n # Keep/adapt/drop ledger — seventeen items reconciled: eight upstream rules\n # carried, five added, three dropped, rationale kept.\n #\n # Carried (six rules below cover eight upstream rules — two of them each\n # carry two upstream rules):\n # - the answer-first rule below carries both upstream's answer-first\n # rule and its cut-ruthlessly rule\n # - the never-trim rule below carries both upstream's\n # cut-elaboration-never-a-warning rule and its\n # numbers-and-thresholds-are-exact rule\n # - the deliverable rule carries upstream's answer-versus-deliverable\n # rule, minus its \"can't tell which? treat it as an answer\" tie-break —\n # gtd has no ambiguity to break, every injection site authors a file\n # - the blunt-and-imperative rule, the plain-words rule, and the bold\n # rule map one-to-one to upstream's equivalents\n #\n # Added and sharpened:\n # - five rules with no upstream equivalent: ship the artifact bare,\n # compressing is not dropping, one idea per block, flag risk in one\n # blunt line, never narrate the work\n # - two carried rules sharpened past upstream: the bold rule gains a\n # bold-only-reading test, and the plain-words rule gains a five-word\n # cap on defining an unavoidable term\n #\n # Dropped, each for its own reason:\n # - the arrow-point paragraph format: four of the seven voice-bearing\n # states are machine-parsed and their grammars require literal\n # `- [ ]` rows\n # - the go-deeper escape hatch: a state's own prompt is the request and\n # there is no channel to ask through\n # - the confirm-before-acting rule: every prompt is an instruction and\n # the actor's only output channel is the file\n styleBlock: |-\n This block opens with why density matters, not with rule one: the reader\n has a finite attention budget, and burying the line that mattered fails as\n hard as cutting it. This is motivation, not a rule — it commands nothing\n and never outranks never-trim.\n\n This file is a deliverable, not a chat reply. Its size follows the work —\n write what the task demands, and let padding, not length, be the target of\n any cut.\n\n Lead with the answer or the decision immediately. Skip the throat-clearing\n opener, and never circle back to restate the point once it has landed.\n\n Write in flat, commanding sentences — commit to the claim rather than\n hedging it.\n\n Favor everyday vocabulary over technical shorthand; where a term is\n unavoidable, define it in five words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it. The test: reading only the bold text yields the full point and every\n risk, or the bolding is wrong.\n\n Above every rule in this block: never trim a risk, a number, a threshold,\n or a scoped condition to save space. A claim that only holds under some\n condition ships with that condition. It outranks every other rule here.\n\n Ship the artifact bare. Output only what was asked for — no lead-in, no\n \"here's the…\", no sign-off wrapped around it. A generated file consumed\n as-is turns a wrapper into a defect.\n\n Compressing is not dropping. A task with three load-bearing parts ships all\n three, each shorter — reporting two of three findings has failed the brief,\n not fulfilled it.\n\n One idea per block; break when the idea shifts. Blocks may run any length,\n but one unbroken paragraph is a defect even when short.\n\n Flag risk and uncertainty in one blunt line. This is never-trim's\n constructive twin: that rule forbids deleting a caveat, this one forbids\n dissolving it into hedged prose.\n\n Never narrate the work — do it. Skip lines like \"I will now list the\n concerns\"; a file has no reason to describe its own composition.\n styleFormatContract: |-\n This file is machine-read. Its format contract outranks every style rule\n above it: keep every `##`/`###` heading this state specifies, every\n `- [ ]` checkbox row, and every required marker line exactly as specified.\n\n Do not replace a checkbox list with prose paragraphs. Do not renumber or\n rename a required heading. Do not drop a marker line. A parser reads these\n literally, and a violation refuses the turn outright — this is not a\n polite request, it is the one thing standing between this output and a\n failed parse.\n\n The voice rules above still govern the prose that sits between these\n structural elements. They do not govern the elements themselves.\n\n # The bundled default PERSONAS — one per prompt-bearing machine below. Each\n # is a machine's `system:` value (see src/StateFields.ts's `system` entry):\n # emitted verbatim as that machine's agent-CLI system prompt, stamped onto\n # every one of its own `prompt` states the same way `model:` is. Like every\n # other var, each is overridable via a top-level `.gtdrc` `vars:` key (e.g.\n # `vars: { builderPersona: \"...\" }`) or a `GTD_<NAME>`-style environment\n # variable — nothing below is privileged.\n #\n # `--system-prompt` REPLACES a harness's entire default system prompt; it\n # does not append to it, and it drops the harness's own injected cwd/env/\n # memory-path/git-status block. Each persona is therefore written as a\n # complete, self-contained system prompt: it states the role, gives tool-use\n # conduct (act, don't ask permission — this runs unattended), and tells the\n # agent to orient itself with real commands (there is no injected status\n # block) and to go inspect the commit range or path its turn's own prompt\n # names, rather than expect a diff pasted into the conversation.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. Nobody is watching this turn\n interactively: there is no one to grant permission, confirm a destructive\n command, or approve a plan before you act. Investigate with real commands\n (`git log`, `git show`, `ls`, `cat`) rather than assuming what a file or a\n commit contains, then act.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. Nothing has told you the\n current branch, the recent commit history, or whether the tree is clean —\n there is no injected status block. Run `git status`, `git log`, `ls` and\n similar yourself before you do anything else; that is the normal first\n move of this turn, not an extra step.\n\n The message you receive each turn is an instruction that names commits,\n paths, or file names for you to go inspect yourself — `git show <hash>`,\n `git diff`, reading a file directly — never a rendered diff pasted into\n the conversation. Expect to go look. Across the several turns this\n conversation may span, remember what you already read and settled in an\n earlier turn rather than re-deriving it from a cold read every time —\n unless the turn's own instructions tell you this is a fresh, cold pass.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one present to confirm a step or approve a draft before you commit to\n it. Investigate with real commands rather than assuming a file's contents\n or a decision's status, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block telling you the branch, recent history, or tree state — run\n `git status`, `git log`, `ls` and the like yourself before doing anything\n else.\n\n The message you receive each turn names files and, where relevant, commit\n ranges for you to go read yourself — never a diff or a plan rendered\n inline. Go look before you decide anything. When a later turn in this\n same conversation asks you to mechanically write out package files, work\n from the plan you already settled rather than re-deriving it, unless\n instructed to read it fresh.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended, so act\n on what you find rather than pausing for confirmation. Investigate with\n real commands — `git log`, `git show`, `git diff` — rather than assuming\n what changed.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block: no cwd banner, no environment summary, no memory-file list,\n no git-status readout. Run `git status`, `git log`, `ls` yourself first.\n\n The message you receive each turn names a commit range or a file for you\n to inspect yourself — `git show`, `git diff` against the range it gives\n you — not a diff pasted into the conversation. Go read the actual history\n before you write or judge anything; the range is often wider than the\n single round in front of you, so check what it actually spans rather than\n assuming it matches your last turn.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. Nobody is present to confirm a\n verdict interactively, so investigate with real commands and commit to\n what you find rather than hedging or asking for permission to look.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment summary, no git status handed to\n you. Orient yourself with `git status`, `git log`, `ls` and the like\n before judging anything.\n\n The message you receive each turn names a spec file to read and a commit\n range for you to inspect yourself — `git show`, `git diff` — never a\n diff rendered inline, and the range is often wider than the one package\n you are checking, since it can span everything built so far in this\n process. Go look before you approve or object; do not assume the range\n matches only the most recent change.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you — never wander into another package's files\n or tasks just because you can see them, and never refactor code the\n current task doesn't touch. When fixing, treat the feedback you're given\n as the diagnosis to act on, not a suggestion to second-guess without\n evidence — but if it is simply wrong, say so by discarding it rather than\n forcing a change onto code that doesn't need one.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to approve a plan or confirm a command before you run it. Write\n the failing test, watch it fail, then make it pass — investigate the\n actual current state of the code and the suite with real commands rather\n than assuming either.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment info, no memory-file paths, no git\n status. Run `git status`, `git log`, `ls` yourself before you start\n editing anything.\n\n The message you receive each turn names the package spec, the feedback\n file, or the check output for you to read yourself at its actual path —\n never a diff or a rendered summary handed to you inline. Go read the\n file, and where a commit range is named, go inspect it with `git show` or\n `git diff` rather than expecting the change pasted in front of you.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before everything it touched is\n squashed into a single commit. Most turns you are handed, your job is to\n write the one conventional-commit message that summarizes the whole\n approved change: subject line, imperative mood, a body that explains why\n and what trade-offs were made, not just what changed. Occasionally you\n are handed a late-breaking failing check instead — fix it, focused and\n minimal, before the process can close. Either way, you are aware that\n everything you touch this turn is about to lose its own commit boundary\n and become part of one final message, so make that message earn its\n place as the historical record.\n\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended, so act\n on what you find rather than waiting for confirmation. Investigate the\n actual commit range and the actual failing output with real commands\n rather than assuming either.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no environment summary, no git status. Run\n `git status`, `git log`, `ls` yourself before acting.\n\n The message you receive each turn names a commit range or a feedback file\n for you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff rendered inline into the conversation. Go look\n before you write the commit message or the fix; if you already built the\n range earlier in this same conversation, work from what you already know\n rather than re-deriving it.\n\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts — both `start-gate` and\n # `review-gate` reach this one check, and its script IS the shared\n # `&suiteCheck` anchor (defined once, right here, and aliased by\n # `fix-precheck` and `healthGate.check` below); the `on` rules turn the\n # result into \"halt at blocked\" vs \"proceed to $onGreen\".\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\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 # Sweep the raw review capture — no path from `deciding`/`collecting`\n # reaches this check; this is a leftover sweeper for a capture an\n # earlier, abandoned process may have left committed here (see the\n # `D .gtd/REVIEW_RAW.md` rows below). A no-op when it is already gone\n # or never existed.\n rm -f .gtd/REVIEW_RAW.md\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 .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # `entry: true` on the shared local marks both `entryGate` instances\n # (start-gate/review-gate) as manual entries — only `review-gate.check`\n # actually needs the reachability root (it is otherwise unreachable\n # from `entries.default`), but the shared dedup makes marking one mark\n # both. Accepted as correct: both ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `start-gate` binds the literal\n # empty string, which compiles away to \"no base\" here — it is reached\n # from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n # `file:` is the one field with an implicit prefix: the compiler\n # resolves it under `.gtd/` automatically, so `file: FEEDBACK.md` here\n # sits beside `on: \"A .gtd/FEEDBACK.md\"` above on purpose — every OTHER\n # path in this template (an `on` pattern, a script body, a prompt/\n # message) still spells `.gtd/` in full.\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records `.gtd/FEEDBACK.md` and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the build tail's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── DEDUP: the open-questions check/answer pair (identity-free) ───────────\n # Shared by design and architecture's own gate (`design.gate`/\n # `architecture.gate`): `check`'s script probes whichever of\n # REQUIREMENTS.md/ARCHITECTURE.md currently exists (see the script's own\n # comment) with `gtd check qa --open-questions` and stamps/removes\n # `QUESTIONS.md` on the result; only when it's present does the process\n # rest at `answer`, a human gate. A question-free phase never rests here at\n # all — `check`'s own `on` rows skip straight to `$onNone`. Takes NO `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path cannot be spliced into a shared script body —\n # `file` is instead the PARAM `answer` renders as its own `file:` (the\n # human-facing gate), a different use entirely from the probe.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # gtd check turn — probe the current phase's steering file for\n # unanswered questions; an answered/empty result removes the marker\n # (.gtd/QUESTIONS.md), an unanswered one stamps it fresh. Design and\n # architecture share this one probe: whichever of\n # .gtd/REQUIREMENTS.md/.gtd/ARCHITECTURE.md exists ON DISK right now\n # IS this phase's steering file — unambiguous because\n # architecture.author deletes .gtd/REQUIREMENTS.md in the same turn\n # it writes .gtd/ARCHITECTURE.md, so exactly one of the two exists\n # here.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # ── ENCAPSULATION: the triage phase's one conversation ─────────────────────\n # Groups the diff that started the process into ordered, classified\n # concerns and resolves the PRODUCT ones' open questions that clear the\n # bar — deciding every sub-threshold point itself instead — one machine\n # identity across the whole back-and-forth, so the agent explores the\n # codebase once and the human's answer rationale survives every return lap.\n # `triage`'s prompt reads THREE laps off the requirements file's own\n # content, never off which edge landed here (the resting state is the same\n # either way): FIRST (the file is absent), RETURN (a ticked/answered\n # question waiting to be folded), and REVIEW LOOP-BACK (concerns already\n # present, nothing ticked-but-unfolded — `re-unwind` put them there, not an\n # answer). The loop-back lap also runs the suite itself and files any\n # breakage it finds as its own FIRST concern: `re-unwind` sits between\n # `build.review.deciding` and here with no `entryGate` in between, so a red\n # the revert causes is otherwise ambient. Left for\n # `packages.item.health.check` to trip over instead, it would spend one of\n # `packages.item.fix-suite`'s retry cap — process-pooled across every\n # package, the same pattern `specReview.review`'s own retry comment\n # documents — on breakage the package itself never caused.\n # ▸ planner identity — `model:` declared once here, stamped onto `triage`\n # (its only prompt state; `gate.answer` is a human turn and carries no\n # prompt).\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.designPersona %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # An agent that deletes the assembled review input without folding it\n # in is the original \"captured then discarded\" bug, now against THIS\n # state's own file — the retired `build.addressing` state was the\n # guard's only user before this restructure; this is its new one.\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the HUMAN'S INPUT — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n On the FIRST lap, `.gtd/REQUIREMENTS.md` does not exist\n yet (or holds whatever the human's own change happened to leave\n there). Build it from that start diff:\n\n 1. Group the diff into an ORDERED list of CONCERNS — each one a\n coherent piece of work that can leave the test suite GREEN on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n 2. Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n 3. For every concern's open point, PRODUCT or TECHNICAL, a question\n is warranted only when all three hold: the answer is a genuine\n fork with divergent outcomes — user-visible for a product\n question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already\n settle it (treat anything the entry diff states explicitly — a\n directive in `.gtd/TODO.md`, a committed choice in a hand-edit —\n as already settled, and on a review loop-back lap treat\n whatever the human's own review-round edit or note states\n explicitly the same way); and getting it wrong would survive\n all the way to the human review tail.\n\n Anything below that bar, PRODUCT or TECHNICAL alike, decide\n yourself: record the decision under `## Answered Questions` —\n the same section a human-answered question lands in — as\n `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a\n `## Open Questions` heading near the top and takes this shape:\n one `### <question>` sub-heading each, followed by a checkbox\n list of candidate answers — TWO concrete options plus a final\n free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Raise questions ONLY for product concerns here — a technical\n concern's open points wait for the next phase. Never tick a box\n yourself — the human ticks exactly one per question.\n\n When every concern in this lap is TECHNICAL, write no\n `## Open Questions` section at all.\n 4. Fold EVERYTHING the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a SKETCH of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n On a RETURN lap the human has ticked one option per question (or\n replaced `_your answer_` with their own text) in\n `.gtd/REQUIREMENTS.md`. For each answered question: fold\n the chosen answer into its concern's prose, then MOVE the question\n down into a `## Answered Questions` section as `### <question>`\n followed by the resolved answer in plain PROSE (drop the\n checkboxes). Never re-raise a question the human deleted; if the\n human deleted the whole `## Open Questions` section, treat every\n product concern as accepted and surface no new questions unless\n something is genuinely still blocking. Omit `## Open Questions`\n entirely once there are none left.\n\n On a REVIEW LOOP-BACK lap, `.gtd/REQUIREMENTS.md`\n already holds concerns and none of them have a ticked-but-unfolded\n answer waiting — that shape means a completed REVIEW ROUND put\n this material here, not a question you asked. Develop those\n existing concerns further with whatever the round raised; never\n rediscover or regroup them from a cold read the way the FIRST lap\n does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On a FIRST lap that\n same hash IS the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on a first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be RED —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the FIRST concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n\n Leave `.gtd/REQUIREMENTS.md` uncommitted and finish your\n turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # ── ENCAPSULATION: the technical phase's one conversation ──────────────────\n # A separate machine from `designPlan` — its own memory scope, since there\n # may be a handover between the user-facing and the technical phase: this\n # phase does NOT resume design's conversation, it reads\n # `.gtd/REQUIREMENTS.md` cold — COLD means no memory of that conversation,\n # never no git access; it still finds and reads the entry commit from\n # history the same way triage does. Resolves the TECHNICAL open questions\n # that clear the same bar triage's own do, deciding every sub-threshold\n # point itself instead, then decomposes the settled concerns into package\n # files. Merge authority lives HERE, in `author` — it is the first state\n # that knows the *how*, so it alone may re-merge concerns whose file\n # footprints coincide (never split); `decompose` remains the mechanical\n # write-out, carrying `author`'s settled — possibly coarsened — grouping\n # over verbatim. ▸ planner identity — `model:` declared once here, stamped\n # onto `author` and `decompose` (its own prompt states; `gate.answer` is a\n # human turn and carries no prompt).\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.architectPersona %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do NOT resume the design conversation — this is a separate\n machine with its own memory, a COLD read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as SETTLED; never re-open it.\n\n COLD means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n On the FIRST lap, develop `.gtd/ARCHITECTURE.md` from\n those concerns: for each one, in the same order, work out the *how*\n — file/module structure, data models, library/tech-stack choices,\n error-handling strategy — building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n For every open TECHNICAL point, a question is warranted\n only when all three hold: the answer is a genuine fork with\n divergent outcomes — user-visible for a product question,\n materially different implementations for a technical one; the\n sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive\n in `.gtd/TODO.md`, a committed choice in a hand-edit — as\n already settled, and on a review loop-back lap treat whatever\n the human's own review-round edit or note states explicitly the\n same way); and getting it wrong would survive all the way to\n the human review tail.\n\n Anything below that bar, decide yourself: record the decision\n under `## Answered Questions` — the same section a\n human-answered question lands in — as\n `### <the point phrased as a question>` followed by the decision\n and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a\n `## Open Questions` heading, exactly as the requirements phase\n does: one `### <question>` each, followed by a checkbox list —\n TWO concrete options plus a final free-text slot, ALL left\n UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one\n option per question (or replaced `_your answer_` with their own\n text): fold each chosen answer into the plan prose, then MOVE the\n question into a `## Answered Questions` section as `### <question>`\n + the resolved answer in plain PROSE (drop the checkboxes). Never\n re-raise a deleted question; treat a deleted `## Open Questions`\n section as acceptance. Omit `## Open Questions` once there are none\n left.\n\n Once `.gtd/ARCHITECTURE.md` is written, delete\n `.gtd/REQUIREMENTS.md` — its content has been folded in\n and it must not linger. Leave everything uncommitted and finish\n your turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ORDERED set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do NOT merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). `collecting` JUDGES whether a\n # round is actionable and, when it is, CLASSIFIES it straight into\n # `REQUIREMENTS.md` as ordered, PRODUCT/TECHNICAL concerns — the same\n # artifact `design.triage` develops — never an instruction list, and never a\n # build. An actionable round still leaves this machine entirely (the root's\n # own `re-unwind`, then `design.triage` folds the concerns in cold), so\n # there is no coder-identity follow-through inside this machine any more.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.reviewerPersona %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). The explanation can go on the line(s) beneath the\n pointer, or trail it on the same line after a dash — both forms are\n valid:\n\n - [ ] ./path/to/file.ts#42\n what this hunk does, with room to run to several lines\n\n - [ ] ./path/to/other.ts#7 — shorter note, same line as the pointer\n\n Either way, the explanation itself must never START with a bare\n `./path` token — that parses as a second hunk pointer, not a note.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\" — it never gates a land, whatever the\n # boxes say. The one dead end that CAN'T be told apart by a file-pattern\n # edge is a deleted REVIEW.md, so the review-doc guard\n # (`src/StepGuards.ts`) catches it BEFORE the step commits and refuses.\n reviewWindow: true\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to collapse the process into\n one commit (**build.squashing**), whatever the boxes say.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (`await-review`'s single\n # `\"* **\": deciding` edge means EVERY human step there lands here, so\n # `deciding` is entered exactly once per round — nothing else commits in\n # between). It is FEEDBACK when the human left\n # a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\" tick) OR a\n # hand-edit to any file this round outside `.gtd/` (every gtd steering\n # file, including REVIEW.md itself, lives there). Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into\n # REVIEW_RAW.md (the `collecting` agent then judges whether it's\n # actionable); it does NOT interpret it. Either way, remove REVIEW.md;\n # what the resulting diff MEANS is decided by this state's own `on`\n # rules — the A/M REVIEW_RAW.md rows are declared BEFORE the D\n # REVIEW.md row so a feedback round (which also deletes REVIEW.md) is\n # captured as feedback, not mistaken for a sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides .gtd/?\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n fi\n rm -f .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n\n # Judges actionability and, when actionable, classifies straight into\n # `REQUIREMENTS.md` — an ordered, PRODUCT/TECHNICAL concern list, never\n # an instruction list for a builder (the actionable path re-plans from\n # history via `re-unwind` + `design.triage`; it never builds on the\n # human's own edit). The outcome is encoded by which paths ITS OWN diff\n # touches, never by a sentinel: writing `REQUIREMENTS.md` (`A`/`M`) IS\n # the actionable case — row ORDER matters, since an actionable round's\n # diff is `A REQUIREMENTS.md` + `D REVIEW_RAW.md` together, so the\n # requirements rows must be declared first or every round would\n # short-circuit to sign-off. CONSUMING `REVIEW_RAW.md` alone (deleting\n # it, the only change this turn) is the non-actionable short-circuit —\n # the same trick `deciding` already uses, one state earlier; it reuses\n # `$onSignoff` (already bound to `build.squashing`) rather than a new\n # param, because the short-circuit IS a sign-off. Declaring no `C` row\n # and no `* **` row is deliberate: a clean turn here is a fruitless\n # dispatch (a crash, a refusal, a context-limit cutoff), never a\n # verdict, so it falls through to the ordinary `prompt`-state default\n # instead — an empty ATTEMPT commit, remembered as a stall\n # (`Edge.ts`'s `stalledAt`) rather than silently misread as \"the human's\n # feedback was non-actionable\" — and a dirty tree matching neither row\n # is a REFUSAL (you classify, you do not build). `REQUIREMENTS.md`\n # survives `re-unwind` (it lives under `.gtd/`, excluded from its own\n # revert pathspec) for `design.triage` to fold in cold; a consumed\n # `REVIEW_RAW.md` with no requirements written is gone before\n # `build.squashing` ever runs, so nothing leaks into the finale.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do NOT change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is ACTIONABLE if any of the following is true:\n\n 1. The human left a NOTE on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a MANDATORY concern below, whether or not\n its box is ticked.\n 2. The human ADDED a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an INSTRUCTION: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a SKETCH, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is NOT actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability to justify a lap, and never dismiss a real note\n or edit as approval.\n\n If the round is ACTIONABLE: write `.gtd/REQUIREMENTS.md`\n with an ORDERED list of CONCERNS, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise NO open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is NOT actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change IS the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # ── ENCAPSULATION: the per-package agentic review ──────────────────────────\n # A single state: a machine whose entire job is to be a separate MIND from\n # the implementer, distinct from the coder identity that builds/fixes the\n # package (`packages.item.*`). ▸ planner identity — `model:` declared once\n # here, stamped onto `review` (its only prompt state). Fixing the reviewer's\n # own findings is a coder action, not a reviewer one — it lives in\n # `packages.item.fix-spec` (`$onFix`), not inside this machine.\n specReview:\n model: <%= it.vars.plannerModel %>\n system: <%~ it.vars.specReviewerPersona %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # Bounded per process: once entered `max` times, a transition into it is\n # redirected to `$onApproved` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `build.fix` cap has (see the README's \"Why\" section, \"Bounded, not\n # runaway\").\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the per-package build queue ─────────────────────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\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 mkdir -p .gtd\n # Sweep the design/architecture phases' spent steering files:\n # REQUIREMENTS.md is consumed by architecture.author, ARCHITECTURE.md\n # by architecture.decompose, and QUESTIONS.md by whichever gate ran\n # last — all three are gone by the time the package queue starts.\n # REVIEW_RAW.md is swept here too: the review loop-back\n # (re-unwind) excludes .gtd from its revert, so an actionable\n # feedback round's MARKED raw capture (see humanReview's own\n # `collecting` state) rides through design and architecture\n # untouched — this is the ONLY sweeper on that path\n # before the queue (and eventually the squash finale) would\n # otherwise pick it up as stray content. No-op if already removed.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Package file names are gtd-authored (architecture.decompose writes\n # NN-name.md), never containing whitespace/newlines that would break\n # ls | head.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the per-package build identity ──────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n system: <%~ it.vars.builderPersona %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (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 `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the process's build identity ─────────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `squashing`\n # and `fix` (its own prompt states). There is no monolithic \"build the whole\n # plan\" state any more — every package is built inside `packages.item`, and\n # the queue drains straight into `build.review`. There is no coder-identity\n # follow-through on review feedback here any more — an actionable round\n # leaves this machine entirely, via `$onFeedback` bound at the root's own\n # `build:` reference to the root-local `re-unwind` state (see the header\n # comment's \"Why humanReview is nested\" section for why the review tail\n # still stays nested here even so). `humanReview` is nested HERE (not at the\n # root) so `build.squashing` — the one coder turn a sign-off still resumes —\n # stays inside the builder's own scope.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: <%~ it.vars.finisherPersona %>\n params: [onDone, onFeedback]\n # Nothing resolves this machine BARE (every reference into it names a\n # specific descendant, e.g. `build.review` / `build.fix`) — but a machine\n # must still declare an `entry:` local per its own grammar, so this names\n # an arbitrary one of its own states.\n entry: fix\n states:\n squashing:\n actor: agent\n label: Squashing\n file: COMMIT_MSG.md\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent. The process is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `.gtd/COMMIT_MSG.md`; write\n it. Do not create other files to hold notes or output.\n\n Leave `.gtd/COMMIT_MSG.md` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `.gtd/COMMIT_MSG.md` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `.gtd/COMMIT_MSG.md` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A .gtd/COMMIT_MSG.md\": $onDone\n \"M .gtd/COMMIT_MSG.md\": $onDone\n\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (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 `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so `squashing`\n # stays inside the builder's own scope on sign-off — see the header\n # comment. Sign-off -> squashing; feedback -> $onFeedback, bound at the\n # root's own `build:` reference to `re-unwind` (a root-local, not a\n # descendant of this machine — the review tail's own scope ends here on\n # the actionable path).\n review:\n machine: humanReview\n with:\n onSignoff: squashing\n onFeedback: $onFeedback\n\n # The root machine — the whole one-flow tree described in this file's\n # header comment. WIRING ONLY: no identity of its own, just the shared\n # entry/exit states and the top-level machine tree.\n unified:\n entry: idle\n states:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n label: Idle\n file: TODO.md\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # ── Green-baseline gate: `gtd --entry fix-precheck` entry ─────────────────\n # `gtd --entry fix-precheck` enters HERE: run the suite, then a red run\n # drops straight into the shared `build.fix` loop (-> build.health.check ->\n # review + squash tail); a green run is a no-op back to `idle` (nothing\n # to fix). Aliases the `&suiteCheck` anchor defined on `entryGate.check`'s\n # own `script:` above — the same plain suite check `start-gate`/\n # `review-gate` run.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(\".gtd/COMMIT_MSG.md\") %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Unwind: revert the entry commit's diff before planning starts ──────\n # Kept a SEPARATE state rather than folded into `start-gate.check`'s own\n # script: the `blocked -> check` retry edge re-runs the gate, so a\n # revert living there would discard the human's own baseline repair and\n # would need an idempotence guard. With exactly one inbound edge (from\n # `idle`), `unwind` is structurally reached once per process and needs\n # no guard at all. Names the reverted commit via `it.currentCommit`\n # (they're the same commit here), NOT a bare `HEAD` — pinning the\n # render-time hash keeps the script correct even if a driver runs it\n # late.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n # gtd check turn — revert the entry commit's diff out of the working\n # tree. Its content survives in history for design.triage to read.\n set +e\n # Hoist the commit hash once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last\n # token on a line.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # ── Re-unwind: revert an actionable review round's hand-edit before ────\n # re-planning starts. A SECOND unwind-shaped state, not a second inbound\n # edge on `unwind` above: `unwind`'s own doc comment turns on it having\n # exactly one inbound edge (from `idle`), which is why it carries no\n # idempotence guard — adding a second caller there would break that\n # invariant. Names the reverted commit via `it.reviewBase`, never\n # `it.currentCommit` (by now that names the assembly turn's own commit)\n # and never a bare `HEAD`: `reviewBaseFor` walks the process trace\n # backwards for the last commit that ENTERED a `reviewBase: true` state\n # — `build.review.deciding` is the template's only one — which is\n # exactly the human's own `await-review -> deciding` commit, the same\n # hash the next review round's incremental base already uses.\n #\n # SCOPED, not wholesale: `deciding` already `rm -f`'d the review file,\n # so a plain `git revert --no-commit` would resurrect it (and conflict\n # on it). Instead this renders that ONE commit's diff with `.gtd/`\n # excluded from the pathspec (REVIEW.md lives there too) and\n # reverse-applies it. The apply is EXPECTED to succeed —\n # nothing between that commit and now touches a code path — but it can\n # still FAIL: a clean-filter round-trip, a drifted worktree, or a binary\n # path can all make `git apply -R` a no-op on an otherwise-clean tree,\n # indistinguishable from a legitimate note-only round. `requireRevert`\n # (below) is what catches that: its step-capture guard\n # (`src/StepGuards.ts`) re-establishes the fact from the tree itself —\n # comparing the human's own commit's paths against the review base's\n # parent — and refuses the step while residue remains, rather than\n # trusting the script's swallowed exit code. A note-only round's patch\n # is EMPTY, and `git apply` on empty input FAILS (\"No valid patches in\n # input\"), hence the `-s` guard rather than `--allow-empty` (which would\n # impose git >= 2.35). The apply is worktree-only, no `--index`: the\n # emitted step script's own `git add -A && git commit` stages it, and\n # `changedPaths` answers by CONTENT, so an unstaged revert is still the\n # pending diff the pattern matches — one fewer index write in a\n # worktree gtd shares with the editor's SCM. The patch file lives under\n # `.gtd/` (excluded from its own pathspec, the same precedent as the\n # check scripts' own `.check-output`) and is removed before the step\n # lands.\n #\n # Both `on` rows target the SAME place (`design`) because either shape\n # is a legitimate outcome here, never a stall: a hand-edited round's\n # patch is non-empty, so the revert leaves a dirty tree (the `\"* **\"`\n # row); a note-only round's patch is empty, so nothing to apply leaves\n # a CLEAN tree — and a clean tree at a `script` state with no `C` row\n # would otherwise be a silent no-op that never advances. Declaring `C`\n # here makes that the expected, terminating case instead.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist ONLY\n # to give the require-revert guard the path it must exempt from residue\n # (the engine blesses no variable names, so the guard reads `Rest.hints.\n # file`, never this literal itself) — the review file is already\n # gone two commits earlier, so this state never renders it. The accepted\n # presentational cost: `gtd next --json`/`gtd land --json` carry that\n # already-deleted path as this state's `file:` hint, and the visualizer\n # labels `re-unwind` with it too. Bounded — `Lsp.ts`'s `buildSteeringMap`\n # skips a mode-less state, so `re-unwind` contributes no path→mode entry\n # and raises no \"already mapped by an earlier state\" warning against\n # `await-review`/`deciding`, which declare the same rendered path with\n # `mode: review`.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # gtd check turn — revert the human's own review edit out of the\n # working tree, scoped to real code: everything under `.gtd/` is\n # excluded, because the review file is already gone by this point\n # and reverting it would resurrect it. The human's intent survives\n # in their own commit for the triage phase to read. The apply is\n # EXPECTED to succeed — nothing between that commit and now touches\n # a code path — but it can still fail on a clean-filter round-trip,\n # a drifted worktree, or a binary path: the `requireRevert`\n # step-capture guard (src/StepGuards.ts) catches that by\n # re-checking the tree itself before this step is allowed to land.\n # The guard's own residue scoping (`isCodePath`) re-derives the same\n # `.gtd/` exemption this pathspec renders — keep the two together\n # when either changes.\n set +e\n # Hoist the reverted commit once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token\n # on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n # ── Green-baseline gate: the single start entry. ───────────────────────\n # Runs on the tree AFTER `unwind`, so this is now just the ordinary\n # green-baseline check `review-gate`/`fix-precheck` also run — no\n # baseline-aware second run, no detached checkout, no HEAD move\n # anywhere in gtd's emitted scripts any more.\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── The triage phase (design.triage -> design.gate.check/.answer -> architecture). ──\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n # ── The technical phase (architecture.author -> architecture.gate.check/.answer ─\n # -> architecture.decompose -> packages). ──────────────────────────────\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n # ── The per-package build queue (packages.* -> build.review). ─────────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── The process's build identity (build.* -> build.review -> done). ───────\n build:\n machine: buildTail\n with:\n onDone: done\n onFeedback: re-unwind\n";
192522
192581
  //#endregion
192523
192582
  //#region src/workflows/templates.ts
192524
192583
  /**
@@ -194378,6 +194437,23 @@ const headAssertion = (expectedHead) => {
194378
194437
  return `[ "$(git rev-parse --verify --quiet HEAD 2>/dev/null)" = ${q} ] || { printf 'gtd: repository changed since this script was generated (expected HEAD %s) — re-run gtd\\n' ${q} >&2; exit 1; }`;
194379
194438
  };
194380
194439
  /**
194440
+ * `[ -f <file> ] || exit 0` — the OR-list guard `src/program.ts`'s
194441
+ * `resolveValidateScript` leads its emitted script with (once the
194442
+ * contradiction round-trip/skip notice, if any, has already run): when the
194443
+ * declared steering file is absent from the working tree — including at a
194444
+ * first-write beat, before the producing agent has written it at all — there
194445
+ * is nothing left to format or validate, so the script exits 0 rather than
194446
+ * running the mode's commands against a file that doesn't exist. An OR list,
194447
+ * never an `if`, for the same `set -eu` reason `headAssertion` documents
194448
+ * above: in an OR list, `-e` exempts every command but the last, so the
194449
+ * failing `[ ... ]` on the left never trips `set -e` on its own — only the
194450
+ * explicit `exit 0` on the right does, and it exits the whole script
194451
+ * cleanly. Exported (alongside `headAssertion`/`reviewWindowAssertion`) so
194452
+ * `src/testing/EmittedScriptRecognizer.ts` can re-derive and string-compare
194453
+ * this exact shape.
194454
+ */
194455
+ const fileExistsGuard = (file) => `[ -f ${shellQuote(file)} ] || exit 0`;
194456
+ /**
194381
194457
  * Same shape as `headAssertion`, for the saved-head ref a review window
194382
194458
  * pins. `--verify --quiet ... 2>/dev/null` makes a MISSING ref read as an
194383
194459
  * empty string rather than erroring the substitution itself — the assertion
@@ -207578,6 +207654,89 @@ const enforceStepGuards = (input) => gen(function* () {
207578
207654
  }
207579
207655
  });
207580
207656
  //#endregion
207657
+ //#region src/ModeContradiction.ts
207658
+ /**
207659
+ * The steering-mode CONTRADICTION round-trip: a pure bash-block builder that
207660
+ * catches a mode whose `format:` command breaks its own `validate:` —
207661
+ * something gtd can detect mechanically without ever touching the real
207662
+ * steering file: format a copy of the format's own canonical sample
207663
+ * (`SteeringFormat.sample`), re-validate the result with `gtd check <mode>`,
207664
+ * and fail loudly (never silently) when the round-trip broke it.
207665
+ *
207666
+ * Pure: no Effect, no git, no filesystem, no ambient reads. Every value this
207667
+ * module needs — the scratch path, the sample bytes, the mode's already-
207668
+ * rendered `format:` command — arrives as an argument; `src/program.ts`
207669
+ * resolves the scratch path (`EnvVars`) and renders the `format:` template
207670
+ * (with `it.file` bound to that scratch path, never the real file), and this
207671
+ * module only concatenates the block those values assemble into.
207672
+ *
207673
+ * `printf '%s'` over a `shellQuote`d sample, never a heredoc — no delimiter
207674
+ * chosen ahead of time can collide with the sample's own bytes. `gtd check
207675
+ * <mode> <samplePath>` is the re-validation: the same in-process parser a
207676
+ * seeded `validate:` command already leaf-invokes, so this adds no new
207677
+ * mechanism and assumes no new binary on `$PATH` beyond `gtd` itself.
207678
+ * `cat`ing the formatted sample to stderr on failure is what gives a human
207679
+ * reader "what the round-trip turned the sample into" — only knowable at run
207680
+ * time, so it can't be baked into the message string ahead of it.
207681
+ *
207682
+ * The message is written for two readers at once, because gtd validate's own
207683
+ * exit code carries no second meaning (see the README's exit-code table) —
207684
+ * the message text is the WHOLE signal distinguishing "the config is broken"
207685
+ * from "the file is malformed". To the agent, first and unmistakably: this is
207686
+ * a configuration bug, not the steering file — do not edit it, stop and end
207687
+ * the turn (without this, an agent handed "validation failed" up to 3 times
207688
+ * under a driver's fix-retry cap will thrash on the document and can mangle
207689
+ * good work before the loop dies). To the human, second: which mode
207690
+ * contradicts itself and the exact rendered `format:` command that ran — the
207691
+ * `cat`'d sample (assembled by the block itself, not this message) is the
207692
+ * third piece, enough together to fix `.gtdrc.json` or the formatter's own
207693
+ * config without reproducing the failure by hand. Deliberately NOT
207694
+ * `src/program.ts`'s `fixPromptInstruction` text (which blames the TURN) and
207695
+ * sharing no wording with it — since the exit code no longer distinguishes
207696
+ * the two cases, reusing that wrapper would erase the distinction completely.
207697
+ */
207698
+ /**
207699
+ * The two-reader message `buildModeContradictionCheck` prints to stderr ahead
207700
+ * of the `cat`'d formatted sample. Exported on its own so a test (or a future
207701
+ * caller) can assert on its exact text without re-deriving the whole block.
207702
+ */
207703
+ const contradictionMessage = (mode, formatCommand) => `gtd: mode "${mode}"'s format: command breaks its own validator — this is a CONFIGURATION BUG, not a problem with the steering file. Do NOT edit the steering file — stop and end your turn now.\nThe rendered format: command that ran:\n${formatCommand}\nWhat it turned mode "${mode}"'s own canonical sample into:`;
207704
+ /**
207705
+ * The round-trip block itself — one self-contained bash fragment with no
207706
+ * blank lines (so `src/Emit.ts`'s `assembleScript` never splits it from a
207707
+ * sibling step), meant to run BEFORE `[ -f <file> ] || exit 0` in the emitted
207708
+ * validate script (see `src/program.ts`'s `resolveValidateScript`) so it still
207709
+ * fires at a first-write beat where the real steering file does not exist
207710
+ * yet. Exits non-zero (after printing the message and the formatted sample)
207711
+ * on a contradiction; otherwise removes the sample and falls through.
207712
+ */
207713
+ const buildModeContradictionCheck = (inputs) => {
207714
+ const { mode, samplePath, sample, formatCommand } = inputs;
207715
+ const pathQ = shellQuote(samplePath);
207716
+ const messageQ = shellQuote(contradictionMessage(mode, formatCommand));
207717
+ return [
207718
+ `printf '%s' ${shellQuote(sample)} > ${pathQ}`,
207719
+ formatCommand,
207720
+ `gtd check ${mode} ${pathQ} >/dev/null 2>&1 || {`,
207721
+ ` printf '%s\\n' ${messageQ} >&2`,
207722
+ ` cat ${pathQ} >&2`,
207723
+ ` rm -f ${pathQ}`,
207724
+ ` exit 1`,
207725
+ `}`,
207726
+ `rm -f ${pathQ}`
207727
+ ].join("\n");
207728
+ };
207729
+ /**
207730
+ * The one-line skip notice for a mode whose validator is EXTERNAL (a user
207731
+ * `validate:` command that is not gtd's own seeded string) — coverage is the
207732
+ * two built-in modes only (`src/SteeringFormats.ts`'s registry), so there is
207733
+ * no in-process parser to round-trip a sample through. Printed to stderr and
207734
+ * nothing else runs — silence would read as a clean bill of health.
207735
+ */
207736
+ const modeContradictionSkipNotice = (mode) => {
207737
+ return `printf '%s\\n' ${shellQuote(`gtd: mode "${mode}" has an external validate: command — skipping the format/validate contradiction check`)} >&2`;
207738
+ };
207739
+ //#endregion
207581
207740
  //#region src/Sh.ts
207582
207741
  /**
207583
207742
  * POSIX single-quote escaping, total over every string: wrap in `'...'`,
@@ -208421,10 +208580,7 @@ const steeringModeSteps = (rest) => gen(function* () {
208421
208580
  if ((yield* RepoFiles).working(file) === void 0) return [];
208422
208581
  const resolved = resolveSteeringMode(rest.def, mode);
208423
208582
  if (resolved === void 0) return yield* fail$2(new Error(unknownModeMessage(rest.def, rest.state, mode)));
208424
- return (yield* renderSteeringCommands(resolved, file, rest.context)).map((command) => ({
208425
- kind: "command",
208426
- command
208427
- }));
208583
+ return yield* renderSteeringModeCommandSteps(resolved, file, rest.context);
208428
208584
  });
208429
208585
  /**
208430
208586
  * The `optional` half: re-open the review checkout window when the step lands
@@ -208803,32 +208959,137 @@ const fixPromptInstruction = (file) => `Your last turn does not pass its own val
208803
208959
  /** True when a rendered rest is a `prompt` turn that hands over a validatable steering file (`file:`+`mode:` both declared). */
208804
208960
  const emitsValidatablePrompt = (rendered) => rendered.kind === "prompt" && rendered.file !== void 0 && rendered.mode !== void 0;
208805
208961
  /**
208962
+ * Render `resolved`'s format:/validate: commands as `EmitStep[]`, wrapping
208963
+ * the LAST one with `onFailure: fixPromptInstruction(file)` when the
208964
+ * validator is a command — shared by `resolveValidateScript`'s own script
208965
+ * (`gtd validate`/`gtd next --json`'s embedded `.validate`) and
208966
+ * `steeringModeSteps`' landing-time one, so the two can never drift on which
208967
+ * command gets the routable fix prompt (package 2's Requirement A: before
208968
+ * this, `steeringModeSteps` carried no `onFailure` at all, so a failing
208969
+ * landing-time validate printed thirteen raw findings a driver couldn't
208970
+ * route).
208971
+ */
208972
+ const renderSteeringModeCommandSteps = (resolved, file, context) => gen(function* () {
208973
+ const commands = yield* renderSteeringCommands(resolved, file, context);
208974
+ const lastIndex = commands.length - 1;
208975
+ return commands.map((command, index) => ({
208976
+ kind: "command",
208977
+ command,
208978
+ ...index === lastIndex && resolved.validate?.kind === "command" ? { onFailure: fixPromptInstruction(file) } : {}
208979
+ }));
208980
+ });
208981
+ /**
208982
+ * The scratch directory a contradiction round-trip's sample is written
208983
+ * under: `EnvVars.all["TMPDIR"]` when set and non-empty, `node:os`'s
208984
+ * `tmpdir()` otherwise — never a `/tmp` literal or `mktemp`
208985
+ * (`tests/tooling/no-tmp-assumption.test.ts` scans `src/` for both). Reading
208986
+ * `EnvVars` first is what makes the emitted script deterministic in unit
208987
+ * tests, which inject a fixed `TMPDIR`.
208988
+ */
208989
+ const scratchDir = (envVars) => {
208990
+ const configured = envVars.all["TMPDIR"];
208991
+ return configured !== void 0 && configured.length > 0 ? configured : tmpdir();
208992
+ };
208993
+ /**
208994
+ * `<scratchDir>/gtd-mode-sample-<mode>-<pid>.md` — an absolute literal baked
208995
+ * in at EMIT time (never re-derived by a shell variable at run time: a
208996
+ * `format:` template like `npx oxfmt --write '<%= it.file %>'` renders
208997
+ * `it.file` inside single quotes, which no shell expands, so `mktemp`/
208998
+ * `${TMPDIR:-/tmp}` forms would never actually resolve). `<pid>` is
208999
+ * `process.pid`, so two concurrent `gtd` processes never collide. The `.md`
209000
+ * suffix is load-bearing — without it a formatter may refuse the file
209001
+ * outright (a raw `format:` failure, not the contradiction finding this
209002
+ * exists to produce).
209003
+ */
209004
+ const scratchSamplePath = (envVars, mode) => join(scratchDir(envVars), `gtd-mode-sample-${mode}-${process.pid}.md`);
209005
+ /**
209006
+ * The contradiction round-trip/skip-notice tier for `resolved`'s `mode:`,
209007
+ * read off `steeringCapabilities` — no new resolution vocabulary (see
209008
+ * `src/SteeringMode.ts`). `formatCommand` absent means nothing to round-trip
209009
+ * (no formatter, no contradiction to find): empty. Otherwise, three cases —
209010
+ * a LIVE built-in validator (covers `qa`/`review` under gtd's seeded
209011
+ * `gtd check <mode> '<file>'` validator, and the `builtin` validator kind)
209012
+ * runs the round-trip against that format's own canonical sample
209013
+ * (`SteeringFormat.sample`); an EXTERNAL validator (a genuine user
209014
+ * `validate:` override, or any command-validated non-built-in mode) prints a
209015
+ * one-line skip notice instead — coverage is the two built-in modes only, and
209016
+ * silence would read as a clean bill of health; a format-only mode with
209017
+ * neither (no in-process parser at all) has nothing to round-trip either:
209018
+ * empty, same as no formatter.
209019
+ *
209020
+ * Emitted BEFORE `Emit.ts`'s `fileExistsGuard` in the caller's step list —
209021
+ * that is the whole reason to use a bundled sample rather than the real file:
209022
+ * it keeps the check alive at a first-write beat where the real steering file
209023
+ * does not exist yet (see the package's own "How" section).
209024
+ */
209025
+ const modeContradictionSteps = (resolved, mode, context) => gen(function* () {
209026
+ if (resolved.formatCommand === void 0) return [];
209027
+ const capabilities = steeringCapabilities(resolved);
209028
+ if (capabilities.format !== void 0 && capabilities.liveValidate !== void 0) {
209029
+ const envVars = yield* EnvVars;
209030
+ const samplePath = scratchSamplePath(envVars, mode);
209031
+ const formatCommand = yield* try_({
209032
+ try: () => renderModeCommand(resolved.formatCommand, {
209033
+ ...context,
209034
+ file: samplePath
209035
+ }),
209036
+ catch: (e) => /* @__PURE__ */ new Error(`mode "${mode}": "format" command failed to render — ${e instanceof Error ? e.message : String(e)}`)
209037
+ });
209038
+ return [{
209039
+ kind: "command",
209040
+ command: buildModeContradictionCheck({
209041
+ mode,
209042
+ samplePath,
209043
+ sample: capabilities.format.sample,
209044
+ formatCommand
209045
+ })
209046
+ }];
209047
+ }
209048
+ if (capabilities.externalValidate === true) return [{
209049
+ kind: "command",
209050
+ command: modeContradictionSkipNotice(mode)
209051
+ }];
209052
+ return [];
209053
+ });
209054
+ /**
208806
209055
  * Resolve the resting state's own steering-file validate script — the SAME
208807
209056
  * script both `gtd validate --json` (`runValidateCommand`, its thin caller
208808
209057
  * now) and `gtd next --json`'s embedded `validate` field emit, from one
208809
209058
  * shared resolver so the two surfaces can't drift. `undefined` = nothing to
208810
- * validate: no `file:`+`mode:` declared, or the declared file is absent from
208811
- * the working tree. An unknown `mode:` FAILS this Effect (exactly as
208812
- * `runValidateCommand` always has) — `runNextCommand` is the one caller that
208813
- * degrades that failure to omitting `validate`, mirroring how the plain-text
208814
- * self-validation instruction already degrades on the same failure.
209059
+ * validate at all: no `file:`+`mode:` declared. An unknown `mode:` FAILS this
209060
+ * Effect (exactly as `runValidateCommand` always has) — `runNextCommand` is
209061
+ * the one caller that degrades that failure to omitting `validate`,
209062
+ * mirroring how the plain-text self-validation instruction already degrades
209063
+ * on the same failure.
209064
+ *
209065
+ * The declared file's PRESENCE is no longer checked here in TS-land: a
209066
+ * missing file used to short-circuit this whole function to `undefined`,
209067
+ * which withheld the repair loop at exactly the first-write beat that needs
209068
+ * it (every `while [ -n "$gtd_validate" ]` driver loop only fires when this
209069
+ * field is non-empty — see the package's own doc comment). Existence is
209070
+ * instead evaluated INSIDE the emitted script itself, via `Emit.ts`'s
209071
+ * `fileExistsGuard`, once it is knowable (after the turn) — an `EmitStep`
209072
+ * list is always produced when `file:`+`mode:` are both declared, even for a
209073
+ * file that turns out absent; that script's own `[ -f <file> ] || exit 0`
209074
+ * then exits 0 with nothing to do, preserving the "a turn that legitimately
209075
+ * wrote nothing burns no fix turns" property. The contradiction round-trip
209076
+ * (`modeContradictionSteps`) is emitted BEFORE that guard, so it still runs
209077
+ * at that same first-write beat, ahead of it.
208815
209078
  */
208816
209079
  const resolveValidateScript = (rest) => gen(function* () {
208817
209080
  const file = rest.hints.file;
208818
209081
  const mode = rest.stateDef.mode;
208819
209082
  if (file === void 0 || mode === void 0) return void 0;
208820
- const fs = yield* FileSystem;
208821
- const toError = (e) => e instanceof Error ? e : new Error(String(e));
208822
- if (!(yield* fs.exists(file).pipe(mapError$1(toError)))) return void 0;
208823
209083
  const resolved = resolveSteeringMode(rest.def, mode);
208824
209084
  if (resolved === void 0) return yield* fail$2(new Error(unknownModeMessage(rest.def, rest.state, mode)));
208825
- const commands = yield* renderSteeringCommands(resolved, file, rest.context);
208826
- const lastIndex = commands.length - 1;
208827
- const steps = commands.map((command, index) => ({
208828
- kind: "command",
208829
- command,
208830
- ...index === lastIndex && resolved.validate?.kind === "command" ? { onFailure: fixPromptInstruction(file) } : {}
208831
- }));
209085
+ const steps = [
209086
+ ...yield* modeContradictionSteps(resolved, mode, rest.context),
209087
+ {
209088
+ kind: "command",
209089
+ command: fileExistsGuard(file)
209090
+ },
209091
+ ...yield* renderSteeringModeCommandSteps(resolved, file, rest.context)
209092
+ ];
208832
209093
  return {
208833
209094
  file,
208834
209095
  mode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "9.10.0",
3
+ "version": "9.10.1",
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": {