@pmelab/gtd 9.5.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -179,41 +179,57 @@ file yourself and `gtd land` — no hand-authored state commit.
179
179
  The process converges on that same shared tail: an agent hands you a
180
180
  `.gtd/REVIEW.md` checkbox review of the diff — the prompt never inlines the diff
181
181
  itself; it names the commit the changes are based at and the agent runs
182
- `git diff` to read the range before writing the review. While the process rests
183
- at that gate, the landing script opens a **review checkout window**: HEAD is
184
- rewound to the review base with the working tree untouched, so the whole
185
- reviewable change shows up as ordinary uncommitted changes in your editor's
186
- normal git integration (and files added during the process show up as ordinary
187
- untracked files, so discarding one deletes it — an untracked file you leave
188
- alone is not a pending change, and only actually removing it from disk counts as
189
- a deletion). The next landing's own script closes the window before it commits.
190
- Tick a box as you review each hunk (ticking just records "I read this"), and
191
- leave a **comment** to request changes: a note on a line, an inline
192
- `// TODO`-style comment in the code, or a direct code edit. A comment sends a
193
- FULL development lap, not a quick fix-and-re-review: an agent first judges
194
- whether your comment is actually actionable (a genuinely approving remark with
195
- no code edit short-circuits straight to sign-off instead), and an actionable
196
- round is re-planned from scratch through triage, architecture, and the package
197
- queue again — a hand-edit you made during review is treated as a **sketch**, the
198
- same as any other change that starts a process, not a fix the agent builds on:
199
- it is reverted out of the tree first, and your intent survives only in your own
200
- review-round commit for triage to read. There is no baseline check on the way
201
- back into planning. If a styled `.gtd/REVIEW.md` ever comes out malformed (a
202
- paragraph where a `- [ ] ./path#line` row belongs), `gtd check review` refuses
203
- the step before it commits: the file stays exactly as written in the working
204
- tree, unlanded, and re-running the same prompt is the recovery — nothing is
205
- lost. Only the turn that drafts the final squash message resumes the session
206
- that built the feature in the first place (the review tail is nested inside that
207
- build identity rather than sitting beside it) — an actionable round leaves that
208
- identity entirely, so it starts a fresh session like any other process. Ticking
209
- every box with no comment is the sign-off, which collapses the whole process
210
- into one commit (a **squash finale** whose message an agent drafts). Landing
211
- with a box still unticked and no comment is refused (finish reviewing first), as
212
- is deleting `.gtd/REVIEW.md`. Both refusals hold wherever the review doc lives:
213
- repointing `reviewFile` out of `.gtd/` (say to `REVIEW.md` at the repo root)
214
- changes nothing about them — your own edit to the review doc is never mistaken
215
- for a code comment, and the doc's pre-turn copy is read at the review window's
216
- saved head rather than at the rewound `HEAD`.
182
+ `git diff` to read the range before writing the review. Each hunk is a
183
+ `- [ ] ./path/to/file.ts#42` pointer with its explanation on the line(s) BELOW
184
+ it, not trailing the pointer on the same line:
185
+
186
+ ```
187
+ - [ ] ./path/to/file.ts#42
188
+ what this hunk does, with room to run to several lines
189
+ ```
190
+
191
+ Text after the pointer token on the pointer's own line is a validation error —
192
+ `gtd check`/`gtd validate`/the LSP all reject it — and, because the review
193
+ gate's own emitted script runs that same check ahead of every commit, upgrading
194
+ gtd mid-review leaves an already-open `.gtd/REVIEW.md` written in the old
195
+ same-line form UNLANDABLE until every pointer in it is hand-edited into the new
196
+ shape; there is no migration tool. If a styled `.gtd/REVIEW.md` ever comes out
197
+ malformed some other way (a paragraph where a `- [ ] ./path#line` row belongs),
198
+ `gtd check review` refuses the step the same way: the file stays exactly as
199
+ written in the working tree, unlanded, and re-running the same prompt is the
200
+ recovery — nothing is lost. While the process rests at that gate, the landing
201
+ script opens a **review checkout window**: HEAD is rewound to the review base
202
+ with the working tree untouched, so the whole reviewable change shows up as
203
+ ordinary uncommitted changes in your editor's normal git integration (and files
204
+ added during the process show up as ordinary untracked files, so discarding one
205
+ deletes it — an untracked file you leave alone is not a pending change, and only
206
+ actually removing it from disk counts as a deletion). The next landing's own
207
+ script closes the window before it commits. Tick a box as you review each hunk
208
+ (ticking just records "I read this"), and leave a **comment** to request
209
+ changes: a note on a line, an inline `// TODO`-style comment in the code, or a
210
+ direct code edit. A comment sends a FULL development lap, not a quick
211
+ fix-and-re-review: an agent first judges whether your comment is actually
212
+ actionable (a genuinely approving remark with no code edit short-circuits
213
+ straight to sign-off instead), and an actionable round is re-planned from
214
+ scratch through triage, architecture, and the package queue again — a hand-edit
215
+ you made during review is treated as a **sketch**, the same as any other change
216
+ that starts a process, not a fix the agent builds on: it is reverted out of the
217
+ tree first, and your intent survives only in your own review-round commit for
218
+ triage to read. There is no baseline check on the way back into planning. Only
219
+ the turn that drafts the final squash message resumes the session that built the
220
+ feature in the first place (the review tail is nested inside that build identity
221
+ rather than sitting beside it) — an actionable round leaves that identity
222
+ entirely, so it starts a fresh session like any other process. A comment is what
223
+ asks for changes; landing with no comment signs off, whatever the boxes say,
224
+ which collapses the whole process into one commit (a **squash finale** whose
225
+ message an agent drafts). Deleting `.gtd/REVIEW.md` is refused, wherever the
226
+ review doc lives: repointing `reviewFile` out of `.gtd/` (say to `REVIEW.md` at
227
+ the repo root) changes nothing about that refusal, since it only asks whether
228
+ the step's diff deletes the file. The exact-path exemption issue #128 fixed
229
+ still matters elsewhere, though: the `deciding` state's own classification and
230
+ the feedback-progress guard both exclude the state's own `file:` by exact path,
231
+ not merely a `.gtd/` prefix, so your own edit to a relocated steering file is
232
+ never mistaken for a code comment.
217
233
 
218
234
  A second entry, the same review tail's own direct entry point —
219
235
  `gtd --entry review-gate.check --var reviewBase=<commitish>` starts a brand new
@@ -859,7 +875,7 @@ its own retry/resume logic beyond "if the script failed, ask gtd again."
859
875
 
860
876
  A mode's `format:` command may reformat a steering file — whitespace, wrapping,
861
877
  reordering — but must NEVER change what a land-capture guard would decide. gtd's
862
- guards (the review sign-off check, the feedback-progress check, the
878
+ guards (the review-doc check, the feedback-progress check, the
863
879
  answer-completeness check, the require-revert check — `src/StepGuards.ts`)
864
880
  decide ONCE, against whichever bytes are on disk at the moment `gtd land` runs,
865
881
  which may be before OR after an emitted script's own `format:` line has run (the
@@ -867,12 +883,14 @@ script runs `format:` then `validate:` then the commit — see
867
883
  `src/SteeringMode.ts`'s `renderSteeringCommands` — but gtd's decision and the
868
884
  driver's script execution are different processes at different times, so there's
869
885
  no guaranteed ordering between "gtd decided" and "the script formatted"). That's
870
- only safe because every built-in guard judges content, not incidental formatting
871
- (e.g. it normalizes `[ ]`/`[x]` checkboxes before comparing). If you plug in
872
- your own `format:` command, the same rule binds it: a formatter that also
873
- changes meaning — ticking a box, stripping a paragraph — makes the guard's
874
- decision and the file's actual content disagree, and gtd will not catch that for
875
- you.
886
+ only safe because every built-in guard judges only the content it explicitly
887
+ cares about, not incidental formatting around it — the feedback-progress guard,
888
+ for instance, only checks whether a deleted file's trimmed first line is the
889
+ `NOTHING ACTIONABLE` sentinel, so reindenting the rest of it changes nothing the
890
+ guard reads. If you plug in your own `format:` command, the same rule binds it:
891
+ a formatter that also changes meaning — stripping a paragraph a guard reads —
892
+ makes the guard's decision and the file's actual content disagree, and gtd will
893
+ not catch that for you.
876
894
 
877
895
  One case never runs your `format:` (or `validate:`) at all: a step whose diff
878
896
  DELETES the state's own `file:`. Deleting it is a legitimate outcome — a review
@@ -191189,9 +191189,9 @@ const questionActions = (content, range) => {
191189
191189
  }
191190
191190
  return actions;
191191
191191
  };
191192
- /** 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). */
191192
+ /** 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. */
191193
191193
  const QA_FORMAT = {
191194
- validate: (content) => parseOpenQuestions(content).errors,
191194
+ validate: (content) => parseOpenQuestions(content).errors.map((message) => ({ message })),
191195
191195
  outline: questionsOutline,
191196
191196
  actions: questionActions
191197
191197
  };
@@ -191200,11 +191200,11 @@ const QA_FORMAT = {
191200
191200
  const HEADER_RE = /^#\s+Review:\s*(\S+)\s*$/;
191201
191201
  const BASE_COMMENT_RE = /^<!--\s*base:\s*(\S+)\s*-->$/;
191202
191202
  const CHUNK_HEADING_RE = /^##\s+(.+)$/;
191203
- /** One `- [ ]`/`- [x]` pointer line: the box, the whitespace-delimited pointer token, and the rest of the line. */
191203
+ /** One `- [ ]`/`- [x]` pointer line: the box, the whitespace-delimited pointer token, and (invalidly) whatever trails it. */
191204
191204
  const FILE_POINTER_RE = /^-\s*\[([ xX])\]\s*(\S+)(?:\s+(.*))?$/;
191205
191205
  /** A pointer token's trailing `#<line>` (greedy, so a `#` inside the path stays in it). */
191206
191206
  const POINTER_LINE_RE = /^(.*)#(\d+)$/;
191207
- /** The optional dash a trailing note may lead with — em dash, en dash, or hyphens. */
191207
+ /** The optional dash a continuation line may lead with — em dash, en dash, or hyphens. */
191208
191208
  const NOTE_SEPARATOR_RE = /^[—–-]+\s*/;
191209
191209
  /** The `# Review: <hash>` header, required as the document's first non-blank line. */
191210
191210
  const parseHeader = (lines) => {
@@ -191218,7 +191218,7 @@ const parseBaseComment = (lines) => {
191218
191218
  if (match) return match[1];
191219
191219
  }
191220
191220
  };
191221
- /** One `- [ ]` / `- [x]` file-pointer line, or `undefined` if `line` isn't one. */
191221
+ /** 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). */
191222
191222
  const parseFilePointer = (line, sourceLine) => {
191223
191223
  const match = FILE_POINTER_RE.exec(line);
191224
191224
  if (!match) return void 0;
@@ -191227,29 +191227,94 @@ const parseFilePointer = (line, sourceLine) => {
191227
191227
  const lineMatch = POINTER_LINE_RE.exec(token);
191228
191228
  const path = lineMatch ? lineMatch[1] : token;
191229
191229
  if (path.length <= 2) return void 0;
191230
- const note = (match[3] ?? "").replace(NOTE_SEPARATOR_RE, "").trim();
191230
+ const trailingText = (match[3] ?? "").trim();
191231
191231
  return {
191232
191232
  checked: match[1] !== " ",
191233
191233
  path,
191234
191234
  ...lineMatch ? { line: Number(lineMatch[2]) } : {},
191235
- ...note.length > 0 ? { note } : {},
191236
- sourceLine
191235
+ sourceLine,
191236
+ ...trailingText.length > 0 ? { trailingText } : {}
191237
191237
  };
191238
191238
  };
191239
- /** Splits one chunk's body lines (up to the next `##` heading) into its file pointers and description prose. */
191240
- const parseChunkBody = (body) => {
191239
+ /**
191240
+ * Index (into `body`) of the last NON-BLANK line belonging to the pointer that
191241
+ * starts at `index`: every following line up to (but excluding) the next
191242
+ * pointer line belongs to this pointer's span. Unlike `OpenQuestions.ts`'s
191243
+ * `itemEndIndex`, a blank line does NOT end the span — the review format
191244
+ * admits multi-paragraph explanations, so a blank line between two
191245
+ * continuation paragraphs stays inside the span; the returned index is just
191246
+ * trimmed back to the last non-blank line so a trailing blank run before the
191247
+ * next pointer/heading isn't absorbed.
191248
+ */
191249
+ const pointerEndIndex = (body, index) => {
191250
+ let end = index;
191251
+ for (let i = index + 1; i < body.length; i += 1) {
191252
+ const trimmed = body[i].text.trim();
191253
+ if (trimmed.length === 0) continue;
191254
+ if (parseFilePointer(trimmed, body[i].line)) break;
191255
+ end = i;
191256
+ }
191257
+ return end;
191258
+ };
191259
+ /** The trailing-text validation error for one pointer whose own line carries text after the pointer token. */
191260
+ const trailingTextError = (title, pointer) => {
191261
+ return {
191262
+ 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`,
191263
+ line: pointer.sourceLine
191264
+ };
191265
+ };
191266
+ /** 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. */
191267
+ const gatherNote = (body, startIndex, endIndex) => {
191268
+ const noteLines = [];
191269
+ for (let j = startIndex + 1; j <= endIndex; j += 1) {
191270
+ const t = body[j].text.trim();
191271
+ if (t.length > 0) noteLines.push(t.replace(NOTE_SEPARATOR_RE, ""));
191272
+ }
191273
+ return noteLines.join(" ").trim();
191274
+ };
191275
+ /** 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. */
191276
+ const parsePointerSpan = (title, body, index, pointer) => {
191277
+ const endIndex = pointerEndIndex(body, index);
191278
+ const note = gatherNote(body, index, endIndex);
191279
+ const { trailingText, ...pointerFields } = pointer;
191280
+ return {
191281
+ file: {
191282
+ ...pointerFields,
191283
+ endLine: body[endIndex].line,
191284
+ ...note.length > 0 ? { note } : {},
191285
+ ...trailingText !== void 0 ? { trailingText } : {}
191286
+ },
191287
+ ...trailingText !== void 0 ? { error: trailingTextError(title, pointer) } : {},
191288
+ nextIndex: endIndex + 1
191289
+ };
191290
+ };
191291
+ /** Splits one chunk's body lines (up to the next `##` heading) into its file pointers (each with its gathered below-the-pointer note and span) and description prose — only the lines before the chunk's FIRST pointer. */
191292
+ const parseChunkBody = (title, body) => {
191241
191293
  const files = [];
191294
+ const errors = [];
191242
191295
  const descriptionLines = [];
191243
- for (const raw of body) {
191244
- const trimmed = raw.text.trim();
191245
- if (trimmed.length === 0) continue;
191246
- const file = parseFilePointer(trimmed, raw.line);
191247
- if (file) files.push(file);
191248
- else descriptionLines.push(trimmed);
191296
+ let i = 0;
191297
+ while (i < body.length) {
191298
+ const trimmed = body[i].text.trim();
191299
+ if (trimmed.length === 0) {
191300
+ i += 1;
191301
+ continue;
191302
+ }
191303
+ const pointer = parseFilePointer(trimmed, body[i].line);
191304
+ if (!pointer) {
191305
+ descriptionLines.push(trimmed);
191306
+ i += 1;
191307
+ continue;
191308
+ }
191309
+ const { file, error, nextIndex } = parsePointerSpan(title, body, i, pointer);
191310
+ files.push(file);
191311
+ if (error) errors.push(error);
191312
+ i = nextIndex;
191249
191313
  }
191250
191314
  return {
191251
191315
  description: descriptionLines.join(" "),
191252
- files
191316
+ files,
191317
+ errors
191253
191318
  };
191254
191319
  };
191255
191320
  /** Splits the document into `##` chunks, each with its title, heading line, and body lines. */
@@ -191281,13 +191346,14 @@ const splitChunks = (lines) => {
191281
191346
  }
191282
191347
  return chunks;
191283
191348
  };
191284
- /** Parses every `##` chunk into a `Changeset`, collecting one error per chunk with no file pointers. */
191349
+ /** Parses every `##` chunk into a `Changeset`, collecting one error per chunk with no file pointers plus each chunk body's own trailing-text errors. */
191285
191350
  const parseChangesets = (lines) => {
191286
191351
  const changesets = [];
191287
191352
  const errors = [];
191288
191353
  for (const { title, headingLine, body } of splitChunks(lines)) {
191289
- const { description, files } = parseChunkBody(body);
191290
- if (files.length === 0) errors.push(`Chunk "${title}" has no file pointers`);
191354
+ const { description, files, errors: bodyErrors } = parseChunkBody(title, body);
191355
+ errors.push(...bodyErrors);
191356
+ if (files.length === 0) errors.push({ message: `Chunk "${title}" has no file pointers` });
191291
191357
  changesets.push({
191292
191358
  title,
191293
191359
  description,
@@ -191295,12 +191361,30 @@ const parseChangesets = (lines) => {
191295
191361
  headingLine
191296
191362
  });
191297
191363
  }
191298
- if (changesets.length === 0) errors.push("REVIEW.md has no '##' chunks");
191364
+ if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
191299
191365
  return {
191300
191366
  changesets,
191301
191367
  errors
191302
191368
  };
191303
191369
  };
191370
+ /** Shared by `parseReviewDoc` and `REVIEW_FORMAT.validate` — the latter needs each finding's `line` (Task 3), which `ReviewDoc.errors` (plain strings, for backward-compatible callers) drops. */
191371
+ const parseReviewFindings = (content) => {
191372
+ const lines = content.split(/\r?\n/);
191373
+ const shortHash = parseHeader(lines);
191374
+ const fullHash = parseBaseComment(lines);
191375
+ const { changesets, errors: chunkFindings } = parseChangesets(lines);
191376
+ const findings = [
191377
+ ...shortHash ? [] : [{ message: "Missing or malformed '# Review: <hash>' header as the document's first line" }],
191378
+ ...fullHash ? [] : [{ message: "Missing '<!-- base: <hash> -->' comment" }],
191379
+ ...chunkFindings
191380
+ ];
191381
+ return {
191382
+ ...shortHash ? { shortHash } : {},
191383
+ ...fullHash ? { fullHash } : {},
191384
+ changesets,
191385
+ findings
191386
+ };
191387
+ };
191304
191388
  /**
191305
191389
  * Parses the review structure out of `content` (the raw text of
191306
191390
  * `.gtd/REVIEW.md`). Total and side-effect-free: always returns a result,
@@ -191309,30 +191393,14 @@ const parseChangesets = (lines) => {
191309
191393
  * exits non-zero with them; the LSP publishes them as diagnostics).
191310
191394
  */
191311
191395
  const parseReviewDoc = (content) => {
191312
- const lines = content.split(/\r?\n/);
191313
- const shortHash = parseHeader(lines);
191314
- const fullHash = parseBaseComment(lines);
191315
- const { changesets, errors: chunkErrors } = parseChangesets(lines);
191316
- const errors = [
191317
- ...shortHash ? [] : ["Missing or malformed '# Review: <hash>' header as the document's first line"],
191318
- ...fullHash ? [] : ["Missing '<!-- base: <hash> -->' comment"],
191319
- ...chunkErrors
191320
- ];
191396
+ const { shortHash, fullHash, changesets, findings } = parseReviewFindings(content);
191321
191397
  return {
191322
191398
  ...shortHash ? { shortHash } : {},
191323
191399
  ...fullHash ? { fullHash } : {},
191324
191400
  changesets,
191325
- errors
191401
+ errors: findings.map((f) => f.message)
191326
191402
  };
191327
191403
  };
191328
- /**
191329
- * Every file pointer in every chunk that is not ticked, over a freshly-parsed
191330
- * document — the structured narrowing the review sign-off guard
191331
- * (`src/StepGuards.ts`) counts instead of a raw `- [ ]` regex over the whole
191332
- * document: a bare `- []`, an indented note, or a non-`./` path outside a
191333
- * chunk's file pointers no longer counts.
191334
- */
191335
- const untickedFiles = (content) => parseReviewDoc(content).changesets.flatMap((chunk) => chunk.files).filter((file) => !file.checked);
191336
191404
  /** Flips the `[ ]`/`[x]` box of the hunk line at `line`, preserving path/note text exactly. */
191337
191405
  const toggleFilePointer = (content, line) => {
191338
191406
  const raw = content.split(/\r?\n/)[line];
@@ -191422,7 +191490,7 @@ const reviewActions = (content, range) => {
191422
191490
  const cursorLine = range.start.line;
191423
191491
  const actions = [];
191424
191492
  changesets.forEach((chunk, i) => {
191425
- const hunk = chunk.files.find((file) => file.sourceLine === cursorLine);
191493
+ const hunk = chunk.files.find((file) => cursorLine >= file.sourceLine && cursorLine <= file.endLine);
191426
191494
  if (hunk) {
191427
191495
  const edit = toggleFilePointer(content, hunk.sourceLine);
191428
191496
  if (edit) actions.push({
@@ -191477,7 +191545,7 @@ const reviewPointerAt = (content, line) => {
191477
191545
  * module that otherwise imports none.
191478
191546
  */
191479
191547
  const STEERING_FORMATS = /* @__PURE__ */ new Map([["qa", QA_FORMAT], ["review", {
191480
- validate: (content) => parseReviewDoc(content).errors,
191548
+ validate: (content) => parseReviewFindings(content).findings,
191481
191549
  outline: reviewOutline,
191482
191550
  actions: reviewActions,
191483
191551
  pointerAt: reviewPointerAt
@@ -192289,7 +192357,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true, f
192289
192357
  };
192290
192358
  //#endregion
192291
192359
  //#region src/workflows/unified.yaml
192292
- var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceModel`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`).\n#\n# * entryGate (dedup ×2) — the green-baseline gate (check + human blocked)\n# guarding the single start (`start-gate`) and\n# the review (`review-gate`) entries. Its check\n# script IS the shared `&suiteCheck` anchor\n# (also aliased by `fix-precheck`, outside this\n# machine) — every entry blocks on any red tree,\n# full stop; there is no baseline-aware second\n# run any more, since `unwind` (below) already\n# put the tree back to the baseline before this\n# gate ever runs. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# build tail's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * questionGate (dedup ×2) — the check/answer pair shared by the design and\n# architecture phases' own open-questions gate\n# (`design.gate.*` / `architecture.gate.*`): a\n# shared probe script decides whether the\n# phase's steering file still has unanswered\n# questions, and only then rests the process at\n# a human answer gate. Identity-free.\n# * designPlan (encap) — the triage phase's one conversation: group the\n# diff that started the process into ordered,\n# classified concerns and resolve the PRODUCT\n# ones' open questions (`design.*`: triage,\n# gate.check, gate.answer). ▸ planner.\n# * archPlan (encap) — the technical phase's one conversation: a COLD\n# read of the settled concerns, resolving the\n# TECHNICAL open questions, then a mechanical\n# decomposition into package files\n# (`architecture.*`: author, gate.check,\n# gate.answer, decompose). A separate machine\n# from `designPlan` (not one fused machine) —\n# its own memory scope, since there may be a\n# handover between the user-facing and the\n# technical phase. ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `buildTail` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — `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 `requirementsFile` for unanswered questions and only\n# rests the process at a human answer gate when some remain — a\n# question-free phase skips the human stop entirely.\n#\n# Once the product questions are settled, `architecture.author` picks up as a\n# COLD reader (a separate machine, a separate memory scope — it reads\n# `requirementsFile` in full rather than resuming design's conversation),\n# develops the *how* for each settled concern, and raises only TECHNICAL open\n# questions — routed through the same shared `questionGate` shape\n# (`architecture.gate`) over `architectureFile`. Once those are settled,\n# `architecture.decompose` is a purely mechanical write-out: one package file\n# per concern, in the settled order — no merge/split judgement, since that\n# already happened in triage's grouping.\n#\n# From there the per-package build queue (`packages.*`) and the shared review\n# + squash tail (`build.*`) are UNCHANGED from before this restructure — see\n# their own machine doc comments below for the per-package build/review/\n# health mechanics.\n#\n# A second entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A third, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All entries converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which 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 (every box ticked, no REVIEW.md\n# note, no code edit) -> 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 `stateDir`) -> `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# Two dead ends never commit: the review-signoff guard (src/StepGuards.ts)\n# REFUSES a step that leaves a box unticked with no comment (finish reviewing\n# first) and a deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health pair (`build.health.check`/`.escalate`, plus its own\n# `build.fix`) 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#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see the README's \"Variables\" section).\n\n# The definition-level declaration `stateDirOf` (src/PatternMachine.ts) reads —\n# a DIFFERENT thing from the `vars.stateDir` below despite sharing a name\n# (see that var's own comment). Rendered from it here so the two stay in\n# lockstep: relocating the var relocates what the engine treats as plumbing.\nstateDir: <%= it.vars.stateDir %>\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both. An ordinary\n # var — the knob a user overrides — distinct from the top-level `stateDir:`\n # declaration above, which renders its value from this one; that is why the\n # two share a name.\n stateDir: .gtd\n # The only per-file var gtd never writes and never reads — the human's own\n # sketch pad, named so the one \"open steering file\" keybinding has an\n # answer at `idle` too.\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n # Stamped (never authored, never read for its content) by design.gate.check\n # / architecture.gate.check — its mere presence is the routing signal that\n # the current phase's steering file still has unanswered questions.\n questionsFile: .gtd/QUESTIONS.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n # Written by packages.item.building when a package's acceptance criteria are\n # already met (an earlier package's fix turn pulled the work in); read by\n # the spec reviewer as the evidence a clean turn there is a real approval,\n # not a lazy/crashed one; removed by packages.item.closing.\n satisfiedFile: .gtd/SATISFIED.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot triage/design/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n # 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\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts — both `start-gate` and\n # `review-gate` reach this one check, and its script IS the shared\n # `&suiteCheck` anchor (defined once, right here, and aliased by\n # `fix-precheck` below); the `on` rules turn the result into \"halt at\n # blocked\" vs \"proceed to $onGreen\". Kept SEPARATE from `healthGate` (below)\n # even though both are a check+something-else shape: this gate runs BEFORE\n # planning, so it must NOT sweep any steering file `healthGate` does.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.stateDir %>/.check-output ]; then\n mv <%~ it.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n # `entry: true` on the shared local marks both `entryGate` instances\n # (start-gate/review-gate) as manual entries — only `review-gate.check`\n # actually needs the reachability root (it is otherwise unreachable\n # from `entries.default`), but the shared dedup makes marking one mark\n # both. Accepted as correct: both ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `start-gate` binds the literal\n # empty string, which compiles away to \"no base\" here — it is reached\n # from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records the configured feedbackFile and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the build tail's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # This script ALSO sweeps the spent raw review capture\n # (`reviewRawFile`) — a mechanic `entryGate.check` must NOT run, since\n # it executes before planning, while a fresh sketch legitimately still\n # exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep the raw review capture — no path from `deciding`/`collecting`\n # reaches this gate any more (its only inbound edge is\n # `gtd --entry fix-precheck`'s red branch); this is a leftover\n # sweeper for a capture an earlier, abandoned process may have left\n # committed on that path. A no-op when it is already gone or never\n # existed.\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n rm -f \"$reviewRaw\"\n <%~ it.vars.testCommand %> > <%~ it.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.stateDir %>/.check-output ]; then\n mv <%~ it.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": $onRed\n \"M <%= it.vars.feedbackFile %>\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── DEDUP: the open-questions check/answer pair (identity-free) ───────────\n # Shared by design and architecture's own gate (`design.gate`/\n # `architecture.gate`): `check`'s script probes whichever of\n # requirementsFile/architectureFile currently exists (see the script's own\n # comment) with `gtd check qa --open-questions` and stamps/removes\n # `questionsFile` on the result; only when it's present does the process\n # rest at `answer`, a human gate. A question-free phase never rests here at\n # all — `check`'s own `on` rows skip straight to `$onNone`. Takes NO `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path cannot be spliced into a shared script body —\n # `file` is instead the PARAM `answer` renders as its own `file:` (the\n # human-facing gate), a different use entirely from the probe.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env bash\n # gtd check turn — probe the current phase's steering file for\n # unanswered questions; an answered/empty result removes the marker\n # (questionsFile), an unanswered one stamps it fresh. Design and\n # architecture share this one probe: whichever of\n # requirementsFile/architectureFile exists ON DISK right now IS this\n # phase's steering file — unambiguous because architecture.author\n # deletes requirementsFile in the same turn it writes\n # architectureFile, so exactly one of the two exists here.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n questions=\"<%~ it.vars.questionsFile %>\"\n mkdir -p \"$(dirname \"$questions\")\"\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n file=\"$requirements\"\n [ -f \"$file\" ] || file=\"$architecture\"\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > \"$questions\"\n # A repeat identical result would otherwise leave the marker\n # byte-identical to its committed copy — no diff, so the check\n # would look answered. Stamp each round with the current HEAD\n # (which always advances between checks) so a still-open re-run\n # always re-registers as an M/A edit and takes the answer edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$questions\"\n else\n rm -f \"$questions\"\n fi\n on:\n \"A <%= it.vars.questionsFile %>\": answer\n \"M <%= it.vars.questionsFile %>\": answer\n \"D <%= it.vars.questionsFile %>\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # ── ENCAPSULATION: the triage phase's one conversation ─────────────────────\n # Groups the diff that started the process into ordered, classified\n # concerns and resolves the PRODUCT ones' open questions — one machine\n # identity across the whole back-and-forth, so the agent explores the\n # codebase once and the human's answer rationale survives every return lap.\n # `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 params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: <%= it.vars.requirementsFile %>\n mode: qa\n # 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 `<%= it.vars.requirementsFile %>`; develop it. Do\n not create other files to hold notes or output.\n\n `<%= it.vars.todoFile %>` 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, `<%= it.vars.requirementsFile %>` does not exist\n yet (or holds whatever the human's own change happened to leave\n there). Build it from that start diff:\n\n 1. Group the diff into an ORDERED list of CONCERNS — each one a\n coherent piece of work that can leave the test suite GREEN on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n 2. Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n 3. For every PRODUCT concern with an open point you cannot settle\n yourself, add a question under a `## Open Questions` heading near\n the top: one `### <question>` sub-heading each, followed by a\n checkbox list of candidate answers — aim for TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Raise questions ONLY for product concerns here — a technical\n concern's open points wait for the next phase. Never tick a box\n yourself — the human ticks exactly one per question.\n 4. Fold EVERYTHING the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a SKETCH of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n On a RETURN lap the human has ticked one option per question (or\n replaced `_your answer_` with their own text) in\n `<%= it.vars.requirementsFile %>`. For each answered question: fold\n the chosen answer into its concern's prose, then MOVE the question\n down into a `## Answered Questions` section as `### <question>`\n followed by the resolved answer in plain PROSE (drop the\n checkboxes). Never re-raise a question the human deleted; if the\n human deleted the whole `## Open Questions` section, treat every\n product concern as accepted and surface no new questions unless\n something is genuinely still blocking. Omit `## Open Questions`\n entirely once there are none left.\n\n On a REVIEW LOOP-BACK lap, `<%= it.vars.requirementsFile %>`\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: `<%= it.vars.requirementsFile %>`\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 `<%= it.vars.requirementsFile %>` uncommitted and finish your\n turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.requirementsFile %>\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `<%= it.vars.requirementsFile %>` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # ── ENCAPSULATION: the technical phase's one conversation ──────────────────\n # A separate machine from `designPlan` — its own memory scope, since there\n # may be a handover between the user-facing and the technical phase: this\n # phase does NOT resume design's conversation, it reads\n # `<%= it.vars.requirementsFile %>` cold. Resolves the TECHNICAL open\n # questions, then mechanically decomposes the settled concerns into package\n # files (no merge/split judgement — that already happened in triage's\n # grouping). ▸ planner identity — `model:` declared once here, stamped onto\n # `author` and `decompose` (its own prompt states; `gate.answer` is a human\n # turn and carries no prompt).\n archPlan:\n model: <%= it.vars.plannerModel %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: <%= it.vars.architectureFile %>\n mode: qa\n prompt: |\n <%~ 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 `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do NOT resume the design conversation — this is a separate\n machine with its own memory, a COLD read every time. Read\n `<%= it.vars.requirementsFile %>` (the settled, ordered, classified\n concerns) in full. Treat every product decision recorded there —\n plain prose or a resolved `## Answered Questions` entry — as\n SETTLED; never re-open it.\n\n On the FIRST lap, develop `<%= it.vars.architectureFile %>` from\n those concerns: for each one, in the same order, work out the *how*\n — file/module structure, data models, library/tech-stack choices,\n error-handling strategy — building on the *what* already settled.\n For every open TECHNICAL point you cannot settle yourself, add a\n question under a `## Open Questions` heading exactly as the\n requirements phase does: one `### <question>` each, followed by a\n checkbox list — TWO concrete options plus a final free-text slot,\n ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one\n option per question (or replaced `_your answer_` with their own\n text): fold each chosen answer into the plan prose, then MOVE the\n question into a `## Answered Questions` section as `### <question>`\n + the resolved answer in plain PROSE (drop the checkboxes). Never\n re-raise a deleted question; treat a deleted `## Open Questions`\n section as acceptance. Omit `## Open Questions` once there are none\n left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in\n and it must not linger. Leave everything uncommitted and finish\n your turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.architectureFile %>\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ 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 `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `<%= it.vars.architectureFile %>` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an ORDERED\n set of concerns with every merge/split judgement made — this turn is\n a mechanical write-out, not a planning one.\n\n Write one package file per concern, in the settled order, under\n `<%= it.vars.packagesDir %>/` (e.g.\n `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Each package file\n carries that concern's requirement (so it can be reviewed against\n its spec), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do NOT merge or\n split concerns here — that judgement already happened upstream;\n carry the settled grouping over verbatim. No package file may\n reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `<%= it.vars.architectureFile %>`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": $onPackages\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). `collecting` JUDGES whether a\n # round is actionable and, when it is, CLASSIFIES it straight into\n # `requirementsFile` 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 entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n <%~ 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 `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the review-signoff guard (`src/StepGuards.ts`)\n # catches them BEFORE the step commits and refuses: an unfinished\n # review (a box still `- [ ]` with no comment) and a deleted REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to collapse the process into\n one commit (**build.squashing**).\n - **Request changes** — leave a comment: a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send 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 Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (the sign-off gate\n # refuses an incomplete step, so `deciding` is entered exactly once per\n # round — nothing commits in between). It is FEEDBACK when the human left\n # a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\" tick) OR a\n # hand-edit to any file this round outside the declared stateDir or the\n # state's own reviewFile (which is var-configurable and may live outside\n # stateDir, e.g. a root-level REVIEW.md — issue #128). Otherwise it is a\n # clean sign-off. This pathspec's plumbing-directory exclusion (stateDir,\n # never a literal `.gtd/`) is the same declaration `src/StepGuards.ts`'s\n # `isCodePath`/`hasCodeChange` re-derive for their own code-vs-plumbing\n # test — keep the two together when either changes. Both exclusions use\n # git's `:(exclude,literal)` pathspec magic, matching stateDir/reviewFile\n # as literal paths rather than regex/glob patterns — a bare grep here\n # would let a metacharacter in either value change what gets excluded.\n # This turn only CAPTURES the raw material into REVIEW_RAW.md (the\n # `collecting` agent then judges whether it's actionable); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n head=$(git rev-parse HEAD)\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it\n # is excluded by exact path, not just the stateDir prefix)?\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \\\n \":(exclude,literal)$stateDir\" \":(exclude,literal)$reviewFile\" \\\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 $reviewFile at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": collecting\n \"M <%= it.vars.reviewRawFile %>\": collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n # Judges actionability and, when actionable, classifies straight into\n # `requirementsFile` — 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 `requirementsFile` (`A`/`M`) IS\n # the actionable case — row ORDER matters, since an actionable round's\n # diff is `A requirementsFile` + `D reviewRawFile` together, so the\n # requirements rows must be declared first or every round would\n # short-circuit to sign-off. CONSUMING `reviewRawFile` 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). `requirementsFile`\n # survives `re-unwind` (it lives under `stateDir`, excluded from its own\n # revert pathspec) for `design.triage` to fold in cold; a consumed\n # `reviewRawFile` 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: <%= it.vars.requirementsFile %>\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 `<%= it.vars.requirementsFile %>` and\n `<%= it.vars.reviewRawFile %>`, 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(it.vars.reviewRawFile) %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is ACTIONABLE if any of the following is true:\n\n 1. The human left a NOTE on `<%= it.vars.reviewFile %>` — 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 `<%= it.vars.requirementsFile %>`\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 `<%= it.vars.reviewRawFile %>` and finish your turn.\n\n If the round is NOT actionable: delete\n `<%= it.vars.reviewRawFile %>` and finish your turn, writing nothing\n to `<%= it.vars.requirementsFile %>` — consuming the capture with no\n other change IS the sign-off.\n on:\n \"A <%= it.vars.requirementsFile %>\": $onFeedback\n \"M <%= it.vars.requirementsFile %>\": $onFeedback\n \"D <%= it.vars.reviewRawFile %>\": $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 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 `<%= it.vars.specFeedbackFile %>`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A <%= it.vars.specFeedbackFile %>\": $onFix\n \"M <%= it.vars.specFeedbackFile %>\": $onFix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the per-package build queue ─────────────────────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the design/architecture phases' spent steering files:\n # requirementsFile is consumed by architecture.author, architectureFile\n # by architecture.decompose, and questionsFile by whichever gate ran\n # last — all three are gone by the time the package queue starts.\n # reviewRawFile is swept here too: the review loop-back\n # (re-unwind) excludes stateDir 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 requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n questions=\"<%~ it.vars.questionsFile %>\"\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n rm -f \"$requirements\" \"$architecture\" \"$questions\" \"$reviewRaw\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the per-package build identity ──────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `<%= it.vars.satisfiedFile %>` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `<%= it.vars.nextFile %>` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `<%= it.vars.satisfiedFile %>` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A <%= it.vars.satisfiedFile %>\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M <%= it.vars.satisfiedFile %>\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %> <%~ it.vars.satisfiedFile %>\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the process's build identity ─────────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `squashing`\n # 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 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: <%= it.vars.commitMsgFile %>\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 `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `<%= it.vars.commitMsgFile %>` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `<%= it.vars.commitMsgFile %>` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A <%= it.vars.commitMsgFile %>\": $onDone\n \"M <%= it.vars.commitMsgFile %>\": $onDone\n\n fix:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so `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: <%= it.vars.todoFile %>\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. <%= it.vars.todoFile %> 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 <%= it.vars.feedbackFile %>\": build.fix\n \"M <%= it.vars.feedbackFile %>\": build.fix\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Unwind: revert the entry commit's diff before planning starts ──────\n # Kept a SEPARATE state rather than folded into `start-gate.check`'s own\n # script: the `blocked -> check` retry edge re-runs the gate, so a\n # revert living there would discard the human's own baseline repair and\n # would need an idempotence guard. With exactly one inbound edge (from\n # `idle`), `unwind` is structurally reached once per process and needs\n # no guard at all. Names the reverted commit via `it.currentCommit`\n # (they're the same commit here), NOT a bare `HEAD` — pinning the\n # render-time hash keeps the script correct even if a driver runs it\n # late.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env bash\n # gtd check turn — revert the entry commit's diff out of the working\n # tree. Its content survives in history for design.triage to read.\n set +e\n # Hoist the commit hash once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last\n # token on a line.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # ── 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 `stateDir`\n # and `reviewFile` excluded from the pathspec (the review file by exact\n # path, since it is var-configurable and may sit at the repo root —\n # issue #128) and 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 # `stateDir` (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: <%= it.vars.reviewFile %>` + `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 `it.vars.reviewFile` 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 status --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: <%= it.vars.reviewFile %>\n requireRevert: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — revert the human's own review edit out of the\n # working tree, scoped to real code: stateDir and reviewFile\n # (var-configurable, may live at the repo root — issue #128) are\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 # stateDir/reviewFile exemption this pathspec renders, from the same\n # declaration (never a literal `.gtd/`) — keep the two together\n # when either changes. Both exclusions carry `:(exclude,literal)`,\n # the same magic `deciding` uses: git's DEFAULT pathspec magic is\n # glob-ish (`*` even crosses `/`), and nothing validates a\n # metacharacter out of either value — `stateDirError` rejects only\n # blanks/root/absolute/`..`/non-canonical segments, and `reviewFile`\n # is an ordinary var. A bare `:(exclude)` therefore lets a `*`/`?`/\n # `[` in either value drop a REAL edited path out of the patch,\n # which the guard (a plain string comparison, not a pathspec) then\n # scores as residue and refuses forever.\n set +e\n commit=\"<%~ it.reviewBase %>\"\n stateDir=\"<%~ it.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n patch=\"$stateDir/.re-unwind.patch\"\n mkdir -p \"$stateDir\"\n git diff --binary \"$commit^\" \"$commit\" -- . \\\n \":(exclude,literal)$stateDir\" \":(exclude,literal)$reviewFile\" > \"$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 `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── The triage phase (design.triage -> design.gate.check/.answer -> architecture). ──\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n # ── The technical phase (architecture.author -> architecture.gate.check/.answer ─\n # -> architecture.decompose -> packages). ──────────────────────────────\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n # ── The per-package build queue (packages.* -> build.review). ─────────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── The process's build identity (build.* -> build.review -> done). ───────\n build:\n machine: buildTail\n with:\n onDone: done\n onFeedback: re-unwind\n";
192360
+ var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See the README's \"How it works\" and \"Driving the loop\" sections for\n# the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this raw source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with MACHINES (entry:/machines:) ───────────────────────────────\n#\n# This template is written as a tree of reusable, parameterized MACHINES (see\n# src/Machines.ts) — a gate/loop written once and instantiated several times\n# with different `with:` bindings (dedup), or a complex cluster grouped under\n# one name for source comprehension (encapsulation). Every reference below is\n# an ordinary `{ machine: <name>, with: { ... } }` local, expanded at\n# compile-time into concrete, qualified states before the pure engine ever\n# sees the definition — no runtime footprint.\n#\n# MACHINE BOUNDARIES LINE UP WITH CONVERSATIONAL IDENTITY: a machine that holds\n# an identity (a planner or a coder persona) declares its own `model:` ONCE, at\n# the machine level (stamped onto every one of its own `prompt` states — see\n# src/Machines.ts's `resolveInstanceModel`), instead of repeating `model:` on\n# every state; no state anywhere declares `model:` or `memory:` directly any\n# more (the memory key is computed from the machine tree — see\n# src/PatternMachine.ts's `memoryScopeAt` / src/Edge.ts's `memoryKeyFor`).\n#\n# * entryGate (dedup ×2) — the green-baseline gate (check + human blocked)\n# guarding the single start (`start-gate`) and\n# the review (`review-gate`) entries. Its check\n# script IS the shared `&suiteCheck` anchor\n# (also aliased by `fix-precheck`, outside this\n# machine) — every entry blocks on any red tree,\n# full stop; there is no baseline-aware second\n# run any more, since `unwind` (below) already\n# put the tree back to the baseline before this\n# gate ever runs. Identity-free.\n# * healthGate (dedup ×2) — the check/escalate health pair shared by the\n# build tail's own tail (`build.health.*`) and\n# the per-package build's own tail\n# (`packages.item.health.*`) — each caller's OWN\n# `fix` state (a coder-identity action) lives\n# beside this machine, not inside it, since\n# fixing IS a coder action and this machine has\n# no identity of its own. Identity-free.\n# * questionGate (dedup ×2) — the check/answer pair shared by the design and\n# architecture phases' own open-questions gate\n# (`design.gate.*` / `architecture.gate.*`): a\n# shared probe script decides whether the\n# phase's steering file still has unanswered\n# questions, and only then rests the process at\n# a human answer gate. Identity-free.\n# * designPlan (encap) — the triage phase's one conversation: group the\n# diff that started the process into ordered,\n# classified concerns and resolve the PRODUCT\n# ones' open questions (`design.*`: triage,\n# gate.check, gate.answer). ▸ planner.\n# * archPlan (encap) — the technical phase's one conversation: a COLD\n# read of the settled concerns, resolving the\n# TECHNICAL open questions, then a mechanical\n# decomposition into package files\n# (`architecture.*`: author, gate.check,\n# gate.answer, decompose). A separate machine\n# from `designPlan` (not one fused machine) —\n# its own memory scope, since there may be a\n# handover between the user-facing and the\n# technical phase. ▸ planner.\n# * humanReview (encap) — the shared review tail, nested INSIDE\n# `buildTail` (`build.review.*`: reviewing →\n# await-review → deciding → collecting) — see\n# \"Why humanReview is nested, not a root sibling\"\n# below. ▸ planner — `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 `requirementsFile` for unanswered questions and only\n# rests the process at a human answer gate when some remain — a\n# question-free phase skips the human stop entirely.\n#\n# Once the product questions are settled, `architecture.author` picks up as a\n# COLD reader (a separate machine, a separate memory scope — it reads\n# `requirementsFile` in full rather than resuming design's conversation),\n# develops the *how* for each settled concern, and raises only TECHNICAL open\n# questions — routed through the same shared `questionGate` shape\n# (`architecture.gate`) over `architectureFile`. Once those are settled,\n# `architecture.decompose` is a purely mechanical write-out: one package file\n# per concern, in the settled order — no merge/split judgement, since that\n# already happened in triage's grouping.\n#\n# From there the per-package build queue (`packages.*`) and the shared review\n# + squash tail (`build.*`) are UNCHANGED from before this restructure — see\n# their own machine doc comments below for the per-package build/review/\n# health mechanics.\n#\n# A second entry, `gtd --entry review-gate.check`, jumps there directly and,\n# once green, to `build.review.reviewing` to review a foreign branch through\n# the same shared tail. A third, `gtd --entry fix-precheck`, jumps there: a\n# red suite drops straight into the shared `build.fix` loop and out through the\n# review + squash tail; a green suite is a no-op back to `idle`. Both states\n# declare `entry: true` — otherwise unreachable from `entries.default`.\n#\n# All entries converge at `build.review.reviewing` -> `build.review.await-review`.\n# Ticking a REVIEW.md box means only \"I reviewed this hunk\"; a COMMENT (not an\n# unticked box) is what asks for changes. Every human step at\n# `build.review.await-review` goes to `build.review.deciding`, which 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 `stateDir`) -> `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#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see the README's \"Variables\" section).\n\n# The definition-level declaration `stateDirOf` (src/PatternMachine.ts) reads —\n# a DIFFERENT thing from the `vars.stateDir` below despite sharing a name\n# (see that var's own comment). Rendered from it here so the two stay in\n# lockstep: relocating the var relocates what the engine treats as plumbing.\nstateDir: <%= it.vars.stateDir %>\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both. An ordinary\n # var — the knob a user overrides — distinct from the top-level `stateDir:`\n # declaration above, which renders its value from this one; that is why the\n # two share a name.\n stateDir: .gtd\n # The only per-file var gtd never writes and never reads — the human's own\n # sketch pad, named so the one \"open steering file\" keybinding has an\n # answer at `idle` too.\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n # Stamped (never authored, never read for its content) by design.gate.check\n # / architecture.gate.check — its mere presence is the routing signal that\n # the current phase's steering file still has unanswered questions.\n questionsFile: .gtd/QUESTIONS.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n # Written by packages.item.building when a package's acceptance criteria are\n # already met (an earlier package's fix turn pulled the work in); read by\n # the spec reviewer as the evidence a clean turn there is a real approval,\n # not a lazy/crashed one; removed by packages.item.closing.\n satisfiedFile: .gtd/SATISFIED.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot triage/design/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n # The fixed diff base for `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` — a plain non-gtd branch reviewed against that\n # commitish rather than the process start. Declared here (rather than left\n # undeclared) so `--var reviewBase=...` passes the CLI's declared-name\n # check; the default empty string renders blank, which the entry command\n # refuses (a review entry always requires an explicit base).\n reviewBase: \"\"\n # 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\nentry:\n default: unified\n\nmachines:\n # ── DEDUP: the green-baseline entry gate (identity-free) ──────────────────\n # Runs the suite before any work starts — both `start-gate` and\n # `review-gate` reach this one check, and its script IS the shared\n # `&suiteCheck` anchor (defined once, right here, and aliased by\n # `fix-precheck` below); the `on` rules turn the result into \"halt at\n # blocked\" vs \"proceed to $onGreen\". Kept SEPARATE from `healthGate` (below)\n # even though both are a check+something-else shape: this gate runs BEFORE\n # planning, so it must NOT sweep any steering file `healthGate` does.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.stateDir %>/.check-output ]; then\n mv <%~ it.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n # `entry: true` on the shared local marks both `entryGate` instances\n # (start-gate/review-gate) as manual entries — only `review-gate.check`\n # actually needs the reachability root (it is otherwise unreachable\n # from `entries.default`), but the shared dedup makes marking one mark\n # both. Accepted as correct: both ARE the workflow's entry gates.\n entry: true\n # `reviewBase: $reviewBase` — only `review-gate` binds a real\n # template (`<%= it.vars.reviewBase %>`), fixing the whole process's\n # diff base to whatever `gtd --entry review-gate.check --var\n # reviewBase=<commitish>` supplies. `start-gate` binds the literal\n # empty string, which compiles away to \"no base\" here — it is reached\n # from `idle`, never manually entered with a base.\n reviewBase: $reviewBase\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the check/escalate health pair (identity-free) ─────────────────\n # Runs the suite; a red run records the configured feedbackFile and routes to\n # the caller's OWN fixer (`$onRed` — a coder-identity action that lives\n # beside this machine, not inside it, since fixing IS a coder action and this\n # machine has no identity); a green run cleans it up and routes to\n # `$onGreen`. Escalation redirects here after the caller's fixer has retried\n # `max` times without success. Instantiated for the build tail's own tail\n # (`build.health.*`, caller's fixer is `build.fix`) and the per-package\n # build's own tail (`packages.item.health.*`, caller's fixer is\n # `packages.item.fix-suite`).\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # This script ALSO sweeps the spent raw review capture\n # (`reviewRawFile`) — a mechanic `entryGate.check` must NOT run, since\n # it executes before planning, while a fresh sketch legitimately still\n # exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep the raw review capture — no path from `deciding`/`collecting`\n # reaches this gate any more (its only inbound edge is\n # `gtd --entry fix-precheck`'s red branch); this is a leftover\n # sweeper for a capture an earlier, abandoned process may have left\n # committed on that path. A no-op when it is already gone or never\n # existed.\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n rm -f \"$reviewRaw\"\n <%~ it.vars.testCommand %> > <%~ it.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.stateDir %>/.check-output ]; then\n mv <%~ it.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": $onRed\n \"M <%= it.vars.feedbackFile %>\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # ── DEDUP: the open-questions check/answer pair (identity-free) ───────────\n # Shared by design and architecture's own gate (`design.gate`/\n # `architecture.gate`): `check`'s script probes whichever of\n # requirementsFile/architectureFile currently exists (see the script's own\n # comment) with `gtd check qa --open-questions` and stamps/removes\n # `questionsFile` on the result; only when it's present does the process\n # rest at `answer`, a human gate. A question-free phase never rests here at\n # all — `check`'s own `on` rows skip straight to `$onNone`. Takes NO `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path cannot be spliced into a shared script body —\n # `file` is instead the PARAM `answer` renders as its own `file:` (the\n # human-facing gate), a different use entirely from the probe.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env bash\n # gtd check turn — probe the current phase's steering file for\n # unanswered questions; an answered/empty result removes the marker\n # (questionsFile), an unanswered one stamps it fresh. Design and\n # architecture share this one probe: whichever of\n # requirementsFile/architectureFile exists ON DISK right now IS this\n # phase's steering file — unambiguous because architecture.author\n # deletes requirementsFile in the same turn it writes\n # architectureFile, so exactly one of the two exists here.\n set +e\n mkdir -p \"<%~ it.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n questions=\"<%~ it.vars.questionsFile %>\"\n mkdir -p \"$(dirname \"$questions\")\"\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n file=\"$requirements\"\n [ -f \"$file\" ] || file=\"$architecture\"\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > \"$questions\"\n # A repeat identical result would otherwise leave the marker\n # byte-identical to its committed copy — no diff, so the check\n # would look answered. Stamp each round with the current HEAD\n # (which always advances between checks) so a still-open re-run\n # always re-registers as an M/A edit and takes the answer edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$questions\"\n else\n rm -f \"$questions\"\n fi\n on:\n \"A <%= it.vars.questionsFile %>\": answer\n \"M <%= it.vars.questionsFile %>\": answer\n \"D <%= it.vars.questionsFile %>\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # ── ENCAPSULATION: the triage phase's one conversation ─────────────────────\n # Groups the diff that started the process into ordered, classified\n # concerns and resolves the PRODUCT ones' open questions — one machine\n # identity across the whole back-and-forth, so the agent explores the\n # codebase once and the human's answer rationale survives every return lap.\n # `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 params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: <%= it.vars.requirementsFile %>\n mode: qa\n # 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 `<%= it.vars.requirementsFile %>`; develop it. Do\n not create other files to hold notes or output.\n\n `<%= it.vars.todoFile %>` 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, `<%= it.vars.requirementsFile %>` does not exist\n yet (or holds whatever the human's own change happened to leave\n there). Build it from that start diff:\n\n 1. Group the diff into an ORDERED list of CONCERNS — each one a\n coherent piece of work that can leave the test suite GREEN on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n 2. Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n 3. For every PRODUCT concern with an open point you cannot settle\n yourself, add a question under a `## Open Questions` heading near\n the top: one `### <question>` sub-heading each, followed by a\n checkbox list of candidate answers — aim for TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Raise questions ONLY for product concerns here — a technical\n concern's open points wait for the next phase. Never tick a box\n yourself — the human ticks exactly one per question.\n 4. Fold EVERYTHING the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a SKETCH of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n On a RETURN lap the human has ticked one option per question (or\n replaced `_your answer_` with their own text) in\n `<%= it.vars.requirementsFile %>`. For each answered question: fold\n the chosen answer into its concern's prose, then MOVE the question\n down into a `## Answered Questions` section as `### <question>`\n followed by the resolved answer in plain PROSE (drop the\n checkboxes). Never re-raise a question the human deleted; if the\n human deleted the whole `## Open Questions` section, treat every\n product concern as accepted and surface no new questions unless\n something is genuinely still blocking. Omit `## Open Questions`\n entirely once there are none left.\n\n On a REVIEW LOOP-BACK lap, `<%= it.vars.requirementsFile %>`\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: `<%= it.vars.requirementsFile %>`\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 `<%= it.vars.requirementsFile %>` uncommitted and finish your\n turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.requirementsFile %>\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `<%= it.vars.requirementsFile %>` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # ── ENCAPSULATION: the technical phase's one conversation ──────────────────\n # A separate machine from `designPlan` — its own memory scope, since there\n # may be a handover between the user-facing and the technical phase: this\n # phase does NOT resume design's conversation, it reads\n # `<%= it.vars.requirementsFile %>` cold. Resolves the TECHNICAL open\n # questions, then mechanically decomposes the settled concerns into package\n # files (no merge/split judgement — that already happened in triage's\n # grouping). ▸ planner identity — `model:` declared once here, stamped onto\n # `author` and `decompose` (its own prompt states; `gate.answer` is a human\n # turn and carries no prompt).\n archPlan:\n model: <%= it.vars.plannerModel %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: <%= it.vars.architectureFile %>\n mode: qa\n prompt: |\n <%~ 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 `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do NOT resume the design conversation — this is a separate\n machine with its own memory, a COLD read every time. Read\n `<%= it.vars.requirementsFile %>` (the settled, ordered, classified\n concerns) in full. Treat every product decision recorded there —\n plain prose or a resolved `## Answered Questions` entry — as\n SETTLED; never re-open it.\n\n On the FIRST lap, develop `<%= it.vars.architectureFile %>` from\n those concerns: for each one, in the same order, work out the *how*\n — file/module structure, data models, library/tech-stack choices,\n error-handling strategy — building on the *what* already settled.\n For every open TECHNICAL point you cannot settle yourself, add a\n question under a `## Open Questions` heading exactly as the\n requirements phase does: one `### <question>` each, followed by a\n checkbox list — TWO concrete options plus a final free-text slot,\n ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one\n option per question (or replaced `_your answer_` with their own\n text): fold each chosen answer into the plan prose, then MOVE the\n question into a `## Answered Questions` section as `### <question>`\n + the resolved answer in plain PROSE (drop the checkboxes). Never\n re-raise a deleted question; treat a deleted `## Open Questions`\n section as acceptance. Omit `## Open Questions` once there are none\n left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in\n and it must not linger. Leave everything uncommitted and finish\n your turn.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: <%= it.vars.architectureFile %>\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ 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 `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `<%= it.vars.architectureFile %>` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an ORDERED\n set of concerns with every merge/split judgement made — this turn is\n a mechanical write-out, not a planning one.\n\n Write one package file per concern, in the settled order, under\n `<%= it.vars.packagesDir %>/` (e.g.\n `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Each package file\n carries that concern's requirement (so it can be reviewed against\n its spec), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do NOT merge or\n split concerns here — that judgement already happened upstream;\n carry the settled grouping over verbatim. No package file may\n reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `<%= it.vars.architectureFile %>`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": $onPackages\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n # ▸ planner identity — `model:` declared once here, stamped onto `reviewing`\n # and `collecting` (its only prompt states). `collecting` JUDGES whether a\n # round is actionable and, when it is, CLASSIFIES it straight into\n # `requirementsFile` 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 entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # `gtd --entry review-gate.check` (a plain non-gtd branch, e.g. a\n # colleague's PR) does NOT enter here directly — it enters\n # `review-gate.check` first, which runs the green-baseline gate and,\n # once green, transitions here. The `Gtd-Review-Base:` trailer rides on that\n # entry commit (the process's oldest), so everything below still reuses\n # the same reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n <%~ 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 `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the changes in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one 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 `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the process rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `deciding` commit — so a\n # feedback round shows only `last-review..HEAD` — or the process start on\n # the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `deciding`, which decides sign-off vs.\n # feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\" — 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 `<%= it.vars.reviewFile %>` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `<%= it.vars.reviewFile %>`, 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 `<%= it.vars.reviewFile %>` 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 `<%= it.vars.reviewFile %>` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the process start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> deciding` commit; HEAD^ is the `reviewing`\n # commit that wrote the agent's original REVIEW.md (`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 the declared stateDir or the\n # state's own reviewFile (which is var-configurable and may live outside\n # stateDir, e.g. a root-level REVIEW.md — issue #128). Otherwise it is a\n # clean sign-off. This pathspec's plumbing-directory exclusion (stateDir,\n # never a literal `.gtd/`) is the same declaration `src/StepGuards.ts`'s\n # `isCodePath`/`hasCodeChange` re-derive for their own code-vs-plumbing\n # test — keep the two together when either changes. Both exclusions use\n # git's `:(exclude,literal)` pathspec magic, matching stateDir/reviewFile\n # as literal paths rather than regex/glob patterns — a bare grep here\n # would let a metacharacter in either value change what gets excluded.\n # This turn only CAPTURES the raw material into REVIEW_RAW.md (the\n # `collecting` agent then judges whether it's actionable); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n head=$(git rev-parse HEAD)\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it\n # is excluded by exact path, not just the stateDir prefix)?\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \\\n \":(exclude,literal)$stateDir\" \":(exclude,literal)$reviewFile\" \\\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 $reviewFile at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": collecting\n \"M <%= it.vars.reviewRawFile %>\": collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n # Judges actionability and, when actionable, classifies straight into\n # `requirementsFile` — 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 `requirementsFile` (`A`/`M`) IS\n # the actionable case — row ORDER matters, since an actionable round's\n # diff is `A requirementsFile` + `D reviewRawFile` together, so the\n # requirements rows must be declared first or every round would\n # short-circuit to sign-off. CONSUMING `reviewRawFile` 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). `requirementsFile`\n # survives `re-unwind` (it lives under `stateDir`, excluded from its own\n # revert pathspec) for `design.triage` to fold in cold; a consumed\n # `reviewRawFile` 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: <%= it.vars.requirementsFile %>\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 `<%= it.vars.requirementsFile %>` and\n `<%= it.vars.reviewRawFile %>`, 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(it.vars.reviewRawFile) %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is ACTIONABLE if any of the following is true:\n\n 1. The human left a NOTE on `<%= it.vars.reviewFile %>` — 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 `<%= it.vars.requirementsFile %>`\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 `<%= it.vars.reviewRawFile %>` and finish your turn.\n\n If the round is NOT actionable: delete\n `<%= it.vars.reviewRawFile %>` and finish your turn, writing nothing\n to `<%= it.vars.requirementsFile %>` — consuming the capture with no\n other change IS the sign-off.\n on:\n \"A <%= it.vars.requirementsFile %>\": $onFeedback\n \"M <%= it.vars.requirementsFile %>\": $onFeedback\n \"D <%= it.vars.reviewRawFile %>\": $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 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 `<%= it.vars.specFeedbackFile %>`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A <%= it.vars.specFeedbackFile %>\": $onFix\n \"M <%= it.vars.specFeedbackFile %>\": $onFix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n # ── ENCAPSULATION: the per-package build queue ─────────────────────────────\n # Identity-free (a bookkeeping queue, not a builder) — the actual per-package\n # build identity lives in its own `item` child (packageItem, below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the design/architecture phases' spent steering files:\n # requirementsFile is consumed by architecture.author, architectureFile\n # by architecture.decompose, and questionsFile by whichever gate ran\n # last — all three are gone by the time the package queue starts.\n # reviewRawFile is swept here too: the review loop-back\n # (re-unwind) excludes stateDir 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 requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n questions=\"<%~ it.vars.questionsFile %>\"\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n rm -f \"$requirements\" \"$architecture\" \"$questions\" \"$reviewRaw\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": item.building\n \"C\": $onDrained\n\n # The per-package build identity — its own coder machine (below), so\n # deleting a package file's own lifecycle stays inside `item` while the\n # queue itself (`packages`) stays a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # ── ENCAPSULATION: the per-package build identity ──────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `building`,\n # `fix-suite`, and `fix-spec` (its own prompt states). `closing` ends that\n # package file's own lifecycle; keeping it here (not in the queue) keeps\n # `packages` itself to a single state. `packages.item.closing` -> `picking`\n # is the ONE upward-resolving target in the whole file, so it MUST be a\n # param (`$onNext`, bound by `packages` — see the machine's own reference).\n packageItem:\n model: <%= it.vars.coderModel %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn may write is `<%= it.vars.satisfiedFile %>` (see below); do NOT\n delete the package file (the spec-review gate reads it after you)\n and never touch `<%= it.vars.nextFile %>` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `<%= it.vars.satisfiedFile %>` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A <%= it.vars.satisfiedFile %>\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M <%= it.vars.satisfiedFile %>\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue. If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback and\n # already-satisfied evidence, so `picking` selects the NEXT package (or\n # falls through to review when the queue is empty). Reached on approval\n # (spec review clean) and on the spec review retry cap (force-close,\n # deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %> <%~ it.vars.satisfiedFile %>\n on:\n \"* **\": $onNext\n\n # Per-package health pair: packages.item.health.check/.escalate ->\n # packages.item.spec.review (green) or packages.item.fix-suite (red).\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Per-package agentic review: packages.item.spec.review -> closing\n # (approved), or packages.item.fix-spec (findings — re-verify via the\n # full health check before re-reviewing).\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # ── ENCAPSULATION: the process's build identity ─────────────────────────────\n # ▸ coder identity — `model:` declared once here, stamped onto `squashing`\n # 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 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: <%= it.vars.commitMsgFile %>\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 `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n if you built that range yourself earlier in this conversation, work from\n what you already know; otherwise read the range yourself. It runs from\n `<%= it.retainedBase %>` to the working tree, and is exactly what that\n commit will contain, the changes THIS process made (its trace/retry\n boundary onward). For a normal build\n process that is the whole feature; for a `gtd --entry review-gate.check`\n process it is only the fixes made during the review — NOT the changeset\n that was under review.\n\n If that range is non-empty, write `<%= it.vars.commitMsgFile %>` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `<%= it.vars.commitMsgFile %>` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A <%= it.vars.commitMsgFile %>\": $onDone\n \"M <%= it.vars.commitMsgFile %>\": $onDone\n\n fix:\n actor: agent\n label: Fixing the check\n file: <%= it.vars.feedbackFile %>\n prompt: |\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n # Shared health pair: build.health.check/.escalate -> build.review.reviewing\n # (green) or build.fix (red).\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # The shared review tail, nested HERE (not at the root) so `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: <%= it.vars.todoFile %>\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. <%= it.vars.todoFile %> 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 <%= it.vars.feedbackFile %>\": build.fix\n \"M <%= it.vars.feedbackFile %>\": build.fix\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: the finale ─────────────────────────────────────────────\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n # ── Unwind: revert the entry commit's diff before planning starts ──────\n # Kept a SEPARATE state rather than folded into `start-gate.check`'s own\n # script: the `blocked -> check` retry edge re-runs the gate, so a\n # revert living there would discard the human's own baseline repair and\n # would need an idempotence guard. With exactly one inbound edge (from\n # `idle`), `unwind` is structurally reached once per process and needs\n # no guard at all. Names the reverted commit via `it.currentCommit`\n # (they're the same commit here), NOT a bare `HEAD` — pinning the\n # render-time hash keeps the script correct even if a driver runs it\n # late.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env bash\n # gtd check turn — revert the entry commit's diff out of the working\n # tree. Its content survives in history for design.triage to read.\n set +e\n # Hoist the commit hash once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last\n # token on a line.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # ── 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 `stateDir`\n # and `reviewFile` excluded from the pathspec (the review file by exact\n # path, since it is var-configurable and may sit at the repo root —\n # issue #128) and 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 # `stateDir` (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: <%= it.vars.reviewFile %>` + `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 `it.vars.reviewFile` 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 status --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: <%= it.vars.reviewFile %>\n requireRevert: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — revert the human's own review edit out of the\n # working tree, scoped to real code: stateDir and reviewFile\n # (var-configurable, may live at the repo root — issue #128) are\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 # stateDir/reviewFile exemption this pathspec renders, from the same\n # declaration (never a literal `.gtd/`) — keep the two together\n # when either changes. Both exclusions carry `:(exclude,literal)`,\n # the same magic `deciding` uses: git's DEFAULT pathspec magic is\n # glob-ish (`*` even crosses `/`), and nothing validates a\n # metacharacter out of either value — `stateDirError` rejects only\n # blanks/root/absolute/`..`/non-canonical segments, and `reviewFile`\n # is an ordinary var. A bare `:(exclude)` therefore lets a `*`/`?`/\n # `[` in either value drop a REAL edited path out of the patch,\n # which the guard (a plain string comparison, not a pathspec) then\n # scores as residue and refuses forever.\n set +e\n commit=\"<%~ it.reviewBase %>\"\n stateDir=\"<%~ it.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n patch=\"$stateDir/.re-unwind.patch\"\n mkdir -p \"$stateDir\"\n git diff --binary \"$commit^\" \"$commit\" -- . \\\n \":(exclude,literal)$stateDir\" \":(exclude,literal)$reviewFile\" > \"$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 `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── Green-baseline gate: `gtd --entry review-gate.check` entry -> build.review.reviewing. ─\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # ── The triage phase (design.triage -> design.gate.check/.answer -> architecture). ──\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n # ── The technical phase (architecture.author -> architecture.gate.check/.answer ─\n # -> architecture.decompose -> packages). ──────────────────────────────\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n # ── The per-package build queue (packages.* -> build.review). ─────────────\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n # ── The process's build identity (build.* -> build.review -> done). ───────\n build:\n machine: buildTail\n with:\n onDone: done\n onFeedback: re-unwind\n";
192293
192361
  //#endregion
192294
192362
  //#region src/workflows/templates.ts
192295
192363
  /**
@@ -192881,9 +192949,9 @@ const blobsAtRef = (exec, ref, paths) => exec("git", "ls-tree", "-r", "-z", ref,
192881
192949
  * stored, and is correctly read as unchanged. Never add `--no-filters` (nor
192882
192950
  * `--path=<other>`) here: that would report every such file `M`, i.e. a
192883
192951
  * spurious "the human edited something real" (`StepGuards`'s `hasCodeChange`),
192884
- * which walks the review-signoff guard past its unticked-box check, plus a
192885
- * spurious change for any `on` pattern to match. Both tiers of the
192886
- * `changedPaths` contract pin this (`src/testing/GitTiers.ts`).
192952
+ * which flips a clean sign-off onto the feedback edge — a full re-plan nobody
192953
+ * asked for — and still fabricates a change for any `on` pattern to match.
192954
+ * Both tiers of the `changedPaths` contract pin this (`src/testing/GitTiers.ts`).
192887
192955
  *
192888
192956
  * A symlink is the one residual inexactness: hash-object reads through it, so it
192889
192957
  * hashes the target's content rather than the link text git stores, and reads as
@@ -192909,8 +192977,8 @@ const hashObjects = (exec, paths) => exec("git", "hash-object", "--", ...paths).
192909
192977
  * through the repo's own clean filters, so a `text=auto` repo's untouched CRLF
192910
192978
  * file matches the LF blob it was stored as. Over-reporting `M` for an untouched
192911
192979
  * file would not be cosmetic: it is a spurious "the human edited something real"
192912
- * (`StepGuards`'s `hasCodeChange`), which sends the review-signoff guard down
192913
- * its it-is-a-comment branch and past the unticked-box check, plus a spurious
192980
+ * (`StepGuards`'s `hasCodeChange`), which flips a clean sign-off onto the
192981
+ * feedback edge — a full re-plan nobody asked for — and still fabricates a
192914
192982
  * change for any `on` pattern to match.
192915
192983
  */
192916
192984
  const classifyUntracked = (exec, ref, untrackedPaths) => gen(function* () {
@@ -206605,10 +206673,10 @@ const toLocation = (root) => (pointer) => ({
206605
206673
  }
206606
206674
  }
206607
206675
  });
206608
- /** One bare finding string → a whole-document `Diagnostic` (a built-in format's `validate` errors carry no per-line position). Not exported on its own — `diagnosticsFor`'s tests cover it in context. */
206609
- const toDiagnostic = (lines) => (message) => ({
206610
- range: spanRange(lines, 0, Math.max(0, lines.length - 1)),
206611
- message,
206676
+ /** One `SteeringFinding` → a `Diagnostic`: a positioned finding underlines exactly its own line, a positionless one spans the whole document. Not exported on its own — `diagnosticsFor`'s tests cover it in context. */
206677
+ const toDiagnostic = (lines) => (finding) => ({
206678
+ range: finding.line !== void 0 ? spanRange(lines, finding.line, finding.line) : spanRange(lines, 0, Math.max(0, lines.length - 1)),
206679
+ message: finding.message,
206612
206680
  severity: import_main.DiagnosticSeverity.Warning,
206613
206681
  source: "gtd"
206614
206682
  });
@@ -207188,7 +207256,7 @@ const openInBrowser = (url) => {
207188
207256
  * `GuardContext.fileDeleted` and `program.ts`'s `steeringModeSteps` ask about a
207189
207257
  * state's `file:`, so they ask it in exactly one place. A deletion is a
207190
207258
  * legitimate step outcome at some states (a review sign-off's bare REVIEW.md
207191
- * deletion) and a refusal at others (see `reviewSignoffGuard`), but either way
207259
+ * deletion) and a refusal at others (see `reviewDocGuard`), but either way
207192
207260
  * there is no file left to format or validate. `steeringModeSteps` widens this
207193
207261
  * same "nothing to format" idea one step further, to a file that was never
207194
207262
  * written at all (not deleted this step, just absent) — that's a separate
@@ -207214,22 +207282,12 @@ const isPlumbingPath = (path, dir) => path === dir || path.startsWith(`${dir}/`)
207214
207282
  * is declared exactly once.
207215
207283
  */
207216
207284
  const isCodePath = (path, file, stateDir) => !isPlumbingPath(path, stateDir) && path !== file;
207217
- /** Normalize every markdown checkbox to a single placeholder so a pure `[ ]`→`[x]` tick is invisible to a text comparison; any surviving difference is a human note. */
207218
- const normalizeCheckboxes = (content) => content.replace(/\[[ xX]\]/g, "[_]");
207219
- /** The `mode:` name of gtd's built-in REVIEW.md checkbox validator — the only mode the sign-off guard understands. */
207285
+ /** The `mode:` name of gtd's built-in REVIEW.md checkbox format — the only mode the review-doc guard understands. */
207220
207286
  const REVIEW_MODE = "review";
207221
- const reviewSignoffGuard = {
207222
- name: "review-signoff",
207287
+ const reviewDocGuard = {
207288
+ name: "review-doc",
207223
207289
  appliesTo: (rest) => isReviewWindowState(rest.def, rest.state) && rest.stateDef.mode === REVIEW_MODE,
207224
- check: (ctx) => gen(function* () {
207225
- if (ctx.fileDeleted) return `${ctx.file} was deleted at "${ctx.rest.state}" — restore it and tick the boxes to sign off, or leave a note (or edit code) to request changes.`;
207226
- if (ctx.hasCodeChange) return void 0;
207227
- const original = (yield* ctx.head) ?? "";
207228
- const current = (yield* ctx.worktree) ?? "";
207229
- if (normalizeCheckboxes(original) !== normalizeCheckboxes(current)) return void 0;
207230
- const unticked = untickedFiles(current).length;
207231
- if (unticked > 0) return `${unticked} review item(s) still unticked and no comment at "${ctx.rest.state}" — finish reviewing (tick every box), or leave a note (or edit code) to request a change.`;
207232
- })
207290
+ check: (ctx) => succeed$3(ctx.fileDeleted ? `${ctx.file} was deleted at "${ctx.rest.state}" — restore it, or leave a note (or edit code) to request changes.` : void 0)
207233
207291
  };
207234
207292
  /** The one-line marker `feedback-collecting` writes when a review round left nothing actionable — the ONLY content that lets a `requireProgress` state's instructions file be deleted without a code change. */
207235
207293
  const NOTHING_ACTIONABLE_SENTINEL = "NOTHING ACTIONABLE";
@@ -207247,7 +207305,7 @@ const feedbackProgressGuard = {
207247
207305
  const QA_MODE = "qa";
207248
207306
  /** The four step-capture guards, in EVALUATION order — message precedence when two would fire is observable, so this order is the contract (matches the old hand-copied call order, minus the now-removed steering-file guard). */
207249
207307
  const stepGuards = [
207250
- reviewSignoffGuard,
207308
+ reviewDocGuard,
207251
207309
  feedbackProgressGuard,
207252
207310
  {
207253
207311
  name: "answer-completeness",
@@ -208338,6 +208396,8 @@ const runValidateCommand = (json, write) => gen(function* () {
208338
208396
  * takes two, but `"review"` there is a distinct, equally wrong, silent
208339
208397
  * mismatch this rejects explicitly rather than accepting).
208340
208398
  */
208399
+ /** One `SteeringFinding` printed the way plain-mode `gtd check`/`gtd validate` show it: `<file>:<line+1>: <message>` for a positioned finding (1-based, editors and humans both count from 1), bare `<message>` for a positionless one. */
208400
+ const formatFinding = (file, finding) => finding.line !== void 0 ? `${file}:${finding.line + 1}: ${finding.message}` : finding.message;
208341
208401
  const runCheckCommand = (mode, file, openQuestions, json, write) => gen(function* () {
208342
208402
  const format = steeringFormatFor(mode);
208343
208403
  if (format === void 0) return yield* fail$2(/* @__PURE__ */ new Error(`gtd check: unknown mode "${mode}" — known modes: ${builtInModeNames().join(", ")}`));
@@ -208347,7 +208407,7 @@ const runCheckCommand = (mode, file, openQuestions, json, write) => gen(function
208347
208407
  }
208348
208408
  const fs = yield* FileSystem;
208349
208409
  const toError = (e) => e instanceof Error ? e : new Error(String(e));
208350
- const errors = (yield* fs.exists(file).pipe(mapError$1(toError))) ? format.validate(yield* fs.readFileString(file).pipe(mapError$1(toError))) : [];
208410
+ const errors = (yield* fs.exists(file).pipe(mapError$1(toError))) ? format.validate(yield* fs.readFileString(file).pipe(mapError$1(toError))).map((finding) => formatFinding(file, finding)) : [];
208351
208411
  if (errors.length === 0) {
208352
208412
  if (json) write(JSON.stringify({
208353
208413
  valid: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "9.5.0",
3
+ "version": "9.6.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {