@pmelab/gtd 12.3.0 → 12.4.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
@@ -325,6 +325,14 @@ noted in steps 2, 3, and 4 below.
325
325
  every one of these correctly: it shows you the message and stops, same as any
326
326
  other question.
327
327
 
328
+ The `gtd --entry fix-precheck` side door (below) repairs a red baseline
329
+ through this same loop's own green check, and that check also runs a
330
+ qualitative review lap first: one configured skill per turn, each looking at
331
+ the change from its own angle (a security checklist, a simplification pass)
332
+ and fixing what it finds once, with no re-review after the fix. It never
333
+ replaces step 4 — your review stays the final gate, and nothing here skips
334
+ it.
335
+
328
336
  A red suite that keeps failing past a few fix attempts escalates instead of
329
337
  retrying forever: an agent turn reads the failing output and writes
330
338
  `.gtd/ESCALATION.md` — what's failing, why the earlier attempts didn't fix
@@ -54306,7 +54306,7 @@ var import_dist$1 = (/* @__PURE__ */ __commonJSMin(((exports) => {
54306
54306
  exports.visit = visit.visit;
54307
54307
  exports.visitAsync = visit.visitAsync;
54308
54308
  })))();
54309
- var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # `healthGate.judge`'s probability floor for the \"identical\" verdict to end\n # a retry loop early — the ONLY off-switch this judged gate has (see\n # 01-judgment-surface.md's Requirement section): a repo retunes or disables\n # it by repointing this var, never by editing the bundled routes: row.\n judgeIdenticalMinP: \"0.7\"\n # `specReview.pre`'s per-section noul: a section answered \"yes\" needs at\n # least this confidence to skip `review` for it; \"no\", or \"yes\" under this\n # floor, both scope the review to that section instead. UNMEASURED: no\n # mined history of \"requirement already satisfied?\" judgments exists to\n # derive it from, and no arithmetic on another threshold can stand in for\n # one — a round of review caught an earlier version deriving this from a\n # value measured for a different question entirely. Chosen conservatively\n # high instead: the pre-judge should rarely fire until real pre-judge\n # history exists to mine a measured value from. Every judged-gate\n # threshold in this file is now an unmeasured conservative default on the\n # same footing.\n specPreJudge: \"0.9\"\n # `humanReview.pre`'s three nouls (mechanicalOnly, touchesPublicAPI,\n # changesBehavior) over `git diff <reviewBase>` — all three must clear this\n # floor at \"yes\"/\"no\"/\"no\" respectively to take the fast path\n # (`fastReview`, skipping the agent's own authoring lap). Unmeasured — no\n # mined history judges this three-way combination — so chosen\n # conservatively high, the same\n # unmeasured-default posture `specPreJudge` documents, until real\n # `humanReview.pre` history exists to mine a value from.\n reviewFastPath: \"0.9\"\n # `humanReview.triage`'s per-chunk noul over `.gtd/REVIEW.md`'s own\n # chunks: \"actionable, not approval or nit?\" A chunk answered \"yes\" below\n # this floor is treated as non-actionable (folded into sign-off) rather\n # than spent on a `collecting` turn — a chunk answered \"no\" is\n # non-actionable regardless of this floor. Blanking it disables the\n # dismissal (every \"yes\" counts, at any confidence) — the same\n # blank-turns-this-judged-gate-off convention `judgeIdenticalMinP` and\n # `specPreJudge` follow, kept in the SAFE direction here (never silently\n # sign off). This gate weighs chunks the HUMAN wrote, evidence no agent in\n # the loop produced — which is why it stays where a post-judge over an\n # agent's own freshly-authored output does not. Unmeasured — chosen\n # conservatively until real `triage` history exists to mine a value from.\n reviewNoteActionable: \"0.7\"\n # `architecture-pre`'s single noul (`architectureWarranted`, over the\n # just-triaged `.gtd/REQUIREMENTS.md`): an answer of \"no\" needs at least\n # this confidence to skip `architecture.author`/`architecture.decompose`\n # entirely; any other outcome (including a skipped judgment) runs the full\n # pass — the conservative default. Unmeasured — chosen conservatively\n # until real `architecture-pre` history exists to mine a value from.\n architectureSkipMinP: \"0.85\"\n # The nine `*Skills` vars below each name a bare, comma-separated skill\n # list for exactly one mapping in the ten authored `skills:` states (see\n # each state's own `skills:` line) — carried verbatim into the rendered\n # `skillsPreamble`, never split or validated by gtd itself. Repointing one\n # in a project config (or its `GTD_<NAME>` env override) changes only that\n # state's preamble.\n triageSkills: spec-driven-development, planning-and-task-breakdown\n architectureSkills: api-and-interface-design, documentation-and-adrs\n decomposeSkills: incremental-implementation, planning-and-task-breakdown\n buildSkills: test-driven-development, incremental-implementation\n fixSkills: debugging-and-error-recovery\n reviewFixSkills: incremental-implementation, code-simplification\n reviewSkills: code-review-and-quality, security-and-hardening, code-simplification\n specReviewSkills: code-review-and-quality, spec-driven-development\n escalateSkills: debugging-and-error-recovery\n # Prepended ahead of a state's own prompt whenever BOTH that state's own\n # `skills:` and this var render non-blank (see `Edge.ts`'s `renderRest`) —\n # blanking this one var switches the whole mechanism off repo-wide without\n # touching any state's own `skills:` line.\n skillsPreamble: |-\n - Load whatever's listed here that your harness actually has installed,\n skip anything it doesn't — silently, never stopping or asking about a\n missing one: <%= it.skills %>\n - A loaded skill offers technique, never authority: this state's own file\n format and its own completion condition are the final word over\n anything a skill's instructions say, regardless of which one this\n prompt states first\n - Never let a loaded skill turn this turn interactive — answer nothing,\n ask nothing; this runs unattended, with no one at a keyboard\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n - A deliverable, not a chat reply — size follows the work; cut padding\n - Lead with the answer; never circle back to restate it\n - Flat, commanding sentences — commit to the claim, never hedge\n - Everyday words; define an unavoidable term in five words or fewer, on\n first use\n - Bold carries the load: bold the claim, not the sentence around it —\n the bold text alone must yield the full point and every risk\n - **Never trim a risk, a number, a threshold, or a scoped condition to\n save space — this outranks every other rule here**\n - Ship the artifact bare — no lead-in, no sign-off\n - Compressing is not dropping: three load-bearing parts ship as three,\n each shorter, never as two\n - One idea per block; break when the idea shifts\n - Flag risk in one blunt line, never hedged prose; never narrate — do it\n styleFormatContract: |-\n - Machine-read: its format contract outranks every style rule above —\n keep every `##`/`###` heading, checkbox row, and marker line exactly\n as specified. Never renumber or rename a heading; a parser reads\n these literally and a violation refuses the turn\n - Voice rules above govern only the prose between these elements\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n - You have shell and file tools — bash, read, write, edit — use them\n without asking first; this runs unattended, no one grants permission\n - Investigate with real commands rather than assuming a file's, a\n commit's, or a decision's status — then act on what you find\n - No injected status block — no cwd, no branch, no history, no tree\n state — orient yourself first with `git status`, `git log`, `ls`\n - The turn's message names a commit, a range, or a file to go inspect\n yourself — never a diff or summary inlined into the conversation\n - Across turns one conversation may span, work from what you already\n read and settled, unless told this is a fresh, cold pass\n designPersona: |-\n You are the product-facing planning voice in gtd's build pipeline: turn\n a raw sketch — a hand-edit, a scratch note, or both — into an ordered,\n classified list of concerns, and hold the running product conversation\n with the human it depends on. You are read by that human — write\n plainly — not by a parser, except where a state says otherwise.\n architectPersona: |-\n You are the technical planning voice in gtd's build pipeline: take over\n once product concerns are settled, reading them cold, no carried\n conversation. Work out the *how* per concern — structure, data models,\n tech-stack choices, error handling — raise the technical open\n questions, then write one package spec per concern with no further\n judgement call: the grouping is already decided.\n reviewerPersona: |-\n You are the independent reviewing mind in gtd's build pipeline —\n deliberately separate from whoever wrote the code, with no attachment\n to it. Two turns: write a structured review document grouping a diff\n into chunks; later, classify a round of the human's feedback as\n actionable or just approving, never fixing anything yourself. You judge\n and classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in gtd's build\n pipeline, checking one freshly-built package against its spec. Verify\n only: tasks done, criteria met, code sound and consistent with the\n codebase. Write feedback only when something is genuinely wrong —\n otherwise write nothing; a clean turn IS the approval. Never fix what\n you find — naming it precisely enough for a fix turn is the whole job.\n builderPersona: |-\n You are the TDD implementer in gtd's build pipeline: build one\n package's declared scope end to end, tests first, and come back to fix\n things when redirected — a failing check, or reviewer feedback. Stay\n strictly inside the package in front of you; never touch another\n package's files or code outside the task. Treat feedback as the\n diagnosis to act on; discard it, with reason, only when simply wrong.\n finisherPersona: |-\n You are the closing identity in gtd's build pipeline — the last coder\n before it closes. Fix a late-breaking failing check after sign-off,\n focused and minimal. Every turn lands its own commit — nothing here\n gets squashed away.\n escalationPersona: |-\n You are the diagnosing voice in gtd's build pipeline, called in once a\n check has stayed red past repeated fix attempts. Read the failing\n output, the previous round's, and the code those attempts touched, then\n write down what is failing, why the earlier attempts didn't resolve it,\n and concrete approaches worth trying next. Never fix the code yourself —\n naming the problem precisely enough for the next fix turn is the whole\n job.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n - You are an autonomous coding agent\n - This workflow's own state files are its private scratchpad, never\n project code or documentation\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n - The goal is shared understanding, not a quota or an empty section: a\n question exists to close a gap between what the human wants and\n what the agent is about to build. Ask whenever that gap is open —\n even when the point looks cheap to undo, settled-looking, or\n narrow. This goal outranks what follows; the conditions below are\n signals a gap is real, strong evidence to ask, never permission\n withheld\n - Before writing `## Open Questions`, walk every concern you grouped and\n collect every point above the bar into that ONE section — a question\n held back for a later lap is a bug. One lap is the target; a second is\n the exception\n - Raise first, narrow second: only once every concern above the bar is\n raised into `## Open Questions`, walk that same set once more and\n answer the ones a confident default settles, moving each into\n `## Answered Questions` with its answer. This pass narrows what's\n already raised — it is never license to raise less, and skipping the\n raise to answer straight through is the same bug as holding a\n question back for a later lap\n - Treat each of these as strong evidence a gap is real: it's a genuine\n fork with divergent outcomes (user-visible for product, materially\n different builds for technical); the diff and history don't already\n settle it (treat an explicit `.gtd/TODO.md` directive, a committed\n hand-edit, or — on a loop-back lap — the human's own review-round\n edit/note as settled); or it would be expensive to undo once\n packages are written\n - Where no gap in shared understanding exists, decide it yourself:\n record it under `## Answered Questions` as\n `### <the point phrased as a question>`\n plus a one-line rationale in prose, no checkboxes. That heading\n always comes last, after every other `##` section\n - Above the bar, write it under `## Open Questions` — always the first\n `##` section — as `### <question>` plus a checkbox list: two\n concrete options and a free-text slot, all unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n - Never tick a box yourself — the human ticks exactly one per question\n questionBarReturn: |-\n - This lap continues the same goal as the first: close the gap\n between what the human wants and what gets built. Folding in\n answers and deciding what's left is that same goal continued, not\n a different job\n - The human answered — a ticked box, a free-text answer in place of\n `_your answer_`, a deleted question, or a deleted `## Open Questions`\n section are all answers. Fold each resolved answer into its concern's\n prose, then move the question into `## Answered Questions` as\n `### <question>` with the resolved answer in plain prose — no\n checkboxes\n - `## Answered Questions` is always the last `##` section — a moved\n question lands there, never wherever `## Open Questions` used to sit\n - Never re-raise a deleted question, and never re-open a settled\n `## Answered Questions` entry\n - An answer may earn a follow-up: if it opens a genuinely new fork above\n the bar — one the answer itself created — raise it as a fresh `##\n Open Questions` entry on this same lap. Never restate a question\n already asked, and never treat this as licence to re-open a question\n already settled under `## Answered Questions`\n - Recognise a silent lap from `## Open Questions` still present with\n nothing ticked and nothing else changed — the human's way of saying\n the gap is already closed. That lap ends the questions, whatever the\n goal says: decide every remaining question yourself, move each to\n `## Answered Questions` with a one-line rationale, raise nothing new,\n and leave no `## Open Questions` section behind\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n - The only state file this turn writes is `.gtd/FEEDBACK.md` — no other\n files for notes or output\n - Read `.gtd/FEEDBACK.md` (the failing test output) and fix the code so\n the suite passes\n - When `.gtd/ESCALATION.md` is present, it is a human-reviewed analysis\n of why earlier attempts failed — read it and treat it as the primary\n instruction for this turn. Never edit or delete it yourself, even once\n you believe you've resolved it: only a genuinely green check retires\n it, so an attempt that turns out to be wrong still leaves the next\n turn's instruction in place. Its absence is the ordinary case: an\n unremarkable red round with nothing to escalate yet\n - Delete `.gtd/FEEDBACK.md` either way — once the suite passes, or once\n you have established the feedback was wrong; leaving it in place is\n never the right end state, the next check writes its own\n\n # The human-facing half of footnotes — how to type one, injected into the\n # three human-gate messages (`design.gate.answer`, `architecture.gate.answer`,\n # `build.review.await-review`). `footnoteFoldIn` below is the agent-facing\n # half; the two never share text, different audience and content.\n footnoteRules: |-\n - Leave a footnote anywhere: mark the exact spot with `[^name]` (any\n name, no whitespace or `]`), then define it below as `[^name]:\n explain what you mean` — indent a longer comment's continuation\n lines. (The literal words \"your comment\" are this format's seeded\n placeholder body — a definition still holding them exactly is\n flagged as unfilled, so write your own words there)\n - A footnote is a comment on that exact spot — the hunk, line, or\n paragraph it marks — never a whole-file remark\n - `name` is yours to pick; only a definition's name must be unique in\n this document — the same name may mark more than one spot\n # The agent-facing half — injected into the three prompts that fold a\n # footnote in (`design.triage`, `architecture.author`,\n # `build.review.collecting`). `build.review.reviewing`, the one state that\n # WRITES a review file, references neither tag — the agent never authors a\n # footnote.\n footnoteFoldIn: |-\n - A footnote (`[^name]` plus its `[^name]:` definition) is a comment on\n its exact anchor — fold it in as a mandatory concern described\n against that anchor's hunk or paragraph, never flattened into a\n whole-file remark\n - DELETE it in this same turn — marker and definition together — the\n way a transient hand-written code comment is already treated. Never\n re-read a footnote already acted on\n - A footnote is human input only — you reply in prose; never write one\n yourself\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n - Write the closing message for the process HEAD closes or sits inside —\n for a squash, an amend, or a PR body. Starting cold: read every\n decision out of the commits below, not assumed context\n - Cover the motivation, the decisions, the trade-offs, and the\n high-level architectural changes — never which files changed; `git\n diff --stat` is for that, not this message\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>` — exactly what\n a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # `&suiteCheck` below (the output-capture / empty-output-fallback /\n # HEAD-stamp body, from `<%~ it.vars.testCommand %>` through the\n # trailing if/else) is DUPLICATED — not aliased — into\n # `healthGate.check` further down this file: a YAML alias reuses a\n # whole scalar node, and `healthGate.check`'s script needs its own\n # PRIOR_FEEDBACK.md prologue TEXTUALLY BEFORE that shared body inside\n # the SAME Eta-templated string, which plain YAML has no mechanism to\n # express (no string-concatenation-of-aliases, and the shared body\n # itself still needs live `<%~ it.vars.testCommand %>` interpolation,\n # so pulling it into a `vars:` entry would freeze that reference as\n # dead text instead of evaluating it). A change to this body — the\n # capture/fallback/stamp logic — MUST be mirrored into\n # `healthGate.check`'s copy by hand; neither test suite nor YAML\n # tooling catches a drift between them.\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/judge/escalate trio for build.health.*/packages.item.health.*\n # — the caller's own coder fix-state ($onRed), not this machine, handles\n # red. `check` writes `.gtd/PRIOR_FEEDBACK.md` from the last COMMITTED red\n # round (found by walking history, not HEAD — the intervening fix turn\n # always deletes `.gtd/FEEDBACK.md` from HEAD once it believes it resolved\n # the failure) only when one exists, so the FIRST red round of an episode\n # (nothing to compare against yet) bypasses `judge` straight to `$onRed`,\n # paying for no judgment. `retry:` stays on `$onRed` (see\n # `packageItem`/`buildTail` below) rather than moving onto `judge` here:\n # `judge`'s only direct structural source is `check`, but `fix`/`fix-suite`\n # sits between every pair of `judge` visits and is NOT one of `judge`'s\n # sources, so `episodeVisits` (`PatternMachine.ts`) would reset `judge`'s\n # own count on every single pass and a cap declared here could never fire.\n # `$onRed` has TWO direct sources instead (`check`'s own bypass row, and\n # `judge`'s `routes:` catch-all below) — sound accumulation under\n # `sourcesOf`'s single-hop rule, the same shape the bundled retry-workflow\n # tests already pin.\n healthGate:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.escalationPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onGreen, onRed, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # DUPLICATES (not aliases) `entryGate.check`'s `&suiteCheck` body from\n # `<%~ it.vars.testCommand %>` through the trailing if/else — see the\n # comment on that anchor for why plain YAML/Eta can't express \"this\n # state's own PRIOR_FEEDBACK.md prologue, THEN the shared body\" any\n # other way. Mirror a change to that shared portion here by hand.\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n rm -f .gtd/PRIOR_FEEDBACK.md\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check (same as entryGate.check's own sweep).\n rm -f .gtd/REVIEW_RAW.md\n # Bound the PRIOR_FEEDBACK.md search to the CURRENT episode, not the\n # whole process (`it.startCommit`): HEAD's own subject already reads\n # \"... → <%= it.state %>\" (the commit that just entered this check —\n # `PatternMachine.ts`'s `stateSubject`/`TRANSITION_SEP`), so the\n # SECOND most recent such subject is the last time this exact check\n # was entered before now. Reaching THIS check always requires\n # passing through it (green or red), so that prior entry is never\n # itself carrying an unrelated gate's FEEDBACK.md the way\n # `it.startCommit` (spanning the whole process, every earlier gate\n # included) could. No second match at all means this is the very\n # first visit ever — nothing to bound against, so there is no prior\n # round full stop (never falls back to `it.startCommit`, which would\n # reintroduce exactly the cross-episode leak this bounds against).\n episode_anchor=$(git log --format='%H %s' <%= it.startCommit %>..HEAD \\\n | grep -F -- ' → <%= it.state %>' | sed -n '2p' | cut -d' ' -f1)\n if [ -n \"$episode_anchor\" ]; then\n prior_commit=$(git log --format=%H --diff-filter=AM \"$episode_anchor\"..HEAD -- .gtd/FEEDBACK.md 2>/dev/null | head -n 1)\n if [ -n \"$prior_commit\" ]; then\n git show \"$prior_commit\":.gtd/FEEDBACK.md > .gtd/PRIOR_FEEDBACK.md 2>/dev/null\n fi\n fi\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n rm -f .gtd/PRIOR_FEEDBACK.md\n # `.gtd/ESCALATION.md` is swept ONLY here, on a genuinely green\n # result — never on a still-red round, so an unresolved analysis\n # a fix turn left in place (fixFeedbackPrompt never deletes it)\n # survives every retry within the same episode. That also makes\n # its deletion a reliable \"this episode's escalation budget just\n # reset\" signal: `escalate`'s own script (below) anchors its\n # round count on the most recent such deletion.\n rm -f .gtd/ESCALATION.md\n fi\n on:\n \"A .gtd/PRIOR_FEEDBACK.md\": judge\n \"M .gtd/PRIOR_FEEDBACK.md\": judge\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n # The judged retry: a `choice` between identical/new-failure/progress\n # over `.gtd/FEEDBACK.md` (this round) vs `.gtd/PRIOR_FEEDBACK.md` (the\n # previous round), both committed by `check` above so `it.read` can\n # reach them (the evidence rule — no gathering turn). An unaware driver\n # sees only `message:` and lands with a clean tree, taking the ordinary\n # `C` row to `$onRed` — the same conservative target `routes:`'s own\n # catch-all names, so a skipped judgment and a \"keep fixing\" verdict\n # both land the same place; only the `Gtd-Judge:` trailer (or its\n # absence) tells them apart.\n judge:\n actor: judge\n label: Judging the retry\n message: |\n The check is still red, and this isn't the first round —\n `.gtd/FEEDBACK.md` (this round) and `.gtd/PRIOR_FEEDBACK.md` (the\n previous round) are both on disk. Run `gtd judge answer` and pipe a\n verdict — identical, new-failure, or progress — or land (with or\n without an edit of your own) to accept the conservative default\n (retry the fix) with no verdict recorded.\n judge: |-\n {\"state\": <%~ JSON.stringify({ current: it.read(\".gtd/FEEDBACK.md\"), previous: it.read(\".gtd/PRIOR_FEEDBACK.md\") }) %>, \"questions\": [{\"id\": \"verdict\", \"primitive\": \"choice\", \"instructions\": \"Compare this round's failing check output (current) against the previous round's (previous), both in state. Classify the change.\", \"criteria\": \"identical: the same failure restated, byte-for-byte or the same root cause, IGNORING each round's own trailing `<!-- gtd check <sha> -->` stamp (that line always differs and is not part of the failure). new-failure: a materially different symptom than previous. progress: still red, but measurably closer to green (fewer failures, a later stage reached).\"}]}\n routes:\n - question: verdict\n is: identical\n minP: \"<%~ it.vars.judgeIdenticalMinP %>\"\n to: escalate\n - to: $onRed\n on:\n \"C\": $onRed\n # The skipped-judgment fallback (Task 6) must not stall on a dirty\n # tree: every OTHER check/agent state's own script owns the tree, so\n # a wildcard row would be dead; this one is a `judge` gate, where a\n # human may still touch a file while reading the message instead of\n # running `gtd judge answer`. No verdict landed this turn (an\n # ordinary edit, not `gtd judge answer`) is still the skipped-\n # judgment path: the same conservative $onRed target as \"C\", never\n # a refusal.\n \"* **\": $onRed\n # The round-counting gate: no `message:`/`file:` — a plain `check`\n # actor's script decides, from git history, whether this is a fresh\n # escalation (write a new `.gtd/ESCALATION.md`) or the second one (stop\n # for good). The round count is git history, not `retry:`: `retry:`'s\n # `episodeVisits` (`PatternMachine.ts`) resets a target's count the\n # moment a non-source state appears in the trace, and BOTH the `stop`\n # human gate and the fix turn sit between every pair of arrivals here\n # without being sources of either — so no `retry:` on any of the three\n # states below could ever accumulate across escalation rounds.\n escalate:\n actor: check\n label: Counting escalation rounds\n script: |\n #!/usr/bin/env sh\n set +e\n # Anchor on the most recent commit that DELETED .gtd/ESCALATION.md\n # — under `healthGate.check`'s own script (above), that ONLY ever\n # happens on a genuinely green result, never a still-red round\n # (a still-red round leaves an unresolved analysis untouched for\n # the next fix attempt to read). So this is reliably \"the last\n # time this episode's escalation budget was reset\" — unlike\n # `.gtd/FEEDBACK.md`, which a fix turn deletes on EVERY belief it\n # resolved the check, red or green, and so sits between every\n # pair of escalation arrivals regardless of episode. With no such\n # deletion, the whole process is one unbroken streak since the\n # start.\n anchor=$(git log --format=%H <%= it.startCommit %>..HEAD --diff-filter=D -- .gtd/ESCALATION.md 2>/dev/null | head -n 1)\n if [ -z \"$anchor\" ]; then anchor=<%= it.startCommit %>; fi\n # Counting every --diff-filter=AM commit against .gtd/ESCALATION.md\n # would also count the HUMAN's own edit at `stop` — landing that\n # edit produces an M .gtd/ESCALATION.md commit too, and `stop`'s own\n # message explicitly invites that edit. So instead of the file's\n # diff history, grep commit SUBJECTS for `describe` as the FROM\n # state — `stateSubject`'s \"gtd(actor): from → to\" shape means the\n # commit that lands a `describe` turn's own write always reads\n # \"... build.health.describe → build.health.stop\" (or the\n # packages.item.health equivalent), the same narrowing\n # `healthGate.check`'s own `episode_anchor` uses above — never the\n # human's own \"... build.health.stop → build.fix\" landing at `stop`.\n #\n # No `-- .gtd/ESCALATION.md` pathspec on this count: a `prompt`\n # state's clean step is an ATTEMPT by design, not a no-op\n # (`validateHasCRow`'s own doc comment), so `describe`'s landing\n # commit exists every round even when its write is byte-identical\n # to what already sits in the tree — a pathspec would silently drop\n # that commit from the count (git sees no diff on that path) and\n # the 2-round cap would never fire on a repeatedly identical\n # analysis.\n describe_source='<%= it.state.replace(/\\.escalate$/, \".describe\") %>'\n rounds=$(git log --format='%s' \"$anchor\"..HEAD 2>/dev/null | grep -c -F -- \"$describe_source →\")\n if [ \"$rounds\" -ge 2 ]; then\n # Preserve whatever is already in the tree — including a human's\n # own fresh-instructions edit landed at `exhausted` itself (a\n # \"... exhausted → fix\" commit this filter never matches, so it's\n # never mistaken for a describe round either) — rather than\n # overwriting it with the machine's last analysis. Only restore\n # from history when the file is genuinely missing.\n if [ ! -f .gtd/ESCALATION.md ]; then\n last=$(git log --format='%H %s' \"$anchor\"..HEAD -- .gtd/ESCALATION.md 2>/dev/null \\\n | grep -F -- \"$describe_source →\" | head -n 1 | cut -d' ' -f1)\n git show \"$last\":.gtd/ESCALATION.md > .gtd/ESCALATION.md 2>/dev/null\n fi\n # Stamp with HEAD so this step's own change registers as a real\n # M/A edit even when the tree's content is otherwise unchanged\n # (the common case — nothing else touches the file between\n # rounds), the same technique `healthGate.check`'s own\n # FEEDBACK.md stamp uses.\n printf '\\n<!-- gtd escalate %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/ESCALATION.md\n fi\n on:\n \"A .gtd/ESCALATION.md\": exhausted\n \"M .gtd/ESCALATION.md\": exhausted\n \"C\": describe\n describe:\n actor: agent\n label: Describing the escalation\n file: FEEDBACK.md\n skills: <%= it.vars.escalateSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn writes is `.gtd/ESCALATION.md`\n - Read `.gtd/FEEDBACK.md` (this round's failing check output),\n `.gtd/PRIOR_FEEDBACK.md` when present (an earlier round's — this\n may be the first round, with no prior round to compare), and the\n code your own earlier attempts touched\n - Write `.gtd/ESCALATION.md`: what is failing, why the previous\n attempts did not resolve it, and concrete suggested approaches to\n solve it\n - Never fix the code yourself — this turn only writes the document\n on:\n \"A .gtd/ESCALATION.md\": stop\n \"M .gtd/ESCALATION.md\": stop\n \"C\": stop\n \"* **\": stop\n stop:\n actor: human\n label: $escalateLabel\n file: ESCALATION.md\n message: |\n The agent could not get the check to pass after repeated attempts,\n and has written `.gtd/ESCALATION.md`: what is failing, why the\n previous attempts didn't resolve it, and suggested approaches.\n\n Edit it — narrow it, redirect it, add what you know — or land it\n untouched to hand it to the next fix turn as-is.\n on:\n \"C\": $onRed\n \"* **\": $onRed\n exhausted:\n actor: human\n label: Escalation exhausted\n file: ESCALATION.md\n message: |\n Escalation attempts are exhausted — this is the second round, and\n the check is still red. `.gtd/ESCALATION.md` holds the last\n unresolved analysis, and `.gtd/FEEDBACK.md` the last failing\n output.\n\n Edit `.gtd/ESCALATION.md` with fresh instructions for the next fix\n turn, or land untouched to give it one more attempt at the same\n analysis.\n on:\n \"C\": $onRed\n \"* **\": $onRed\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` is instead the `answer` param's own `file:` (the human gate).\n # `check`'s own script picks between REQUIREMENTS.md/ARCHITECTURE.md by\n # testing which file exists, at runtime, inside the shared script.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"C\":\n to: $onRevise\n action: Accept as-is\n describe: >-\n change nothing and re-run to advance with the questions\n unanswered — the plan stands as written.\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, or delete a question to skip it. To\n accept the plan as-is instead, revert everything and re-run —\n a clean tree is the only accept gesture.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n skills: <%= it.vars.triageSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state file this turn touches is `.gtd/REQUIREMENTS.md`\n — no other files for notes or output\n - `.gtd/TODO.md` is the likely home of the sketch that started\n this process — the human's input, folded into the concerns\n below like any other part of the start diff; never a state\n file to preserve, never gtd bookkeeping to ignore\n - This process started at commit `<%= it.startCommit %>`,\n reverted out of the tree by `unwind` right after landing, so\n `git diff <%= it.startCommit %>` is now empty — its content\n survives only in history. Find the entry commit yourself:\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's first turn, before any baseline-repair\n commits), then `git show` it — a hand-edit, a scratch note,\n or both\n - Whichever lap this is, leave `.gtd/REQUIREMENTS.md`\n uncommitted and finish once it reflects this lap's own work\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's change left there). Build it from that start diff into an\n ordered list of concerns, each classified below.\n\n ### Classify each concern\n\n Classify each as PRODUCT (user-facing/requirements) or TECHNICAL\n (implementation).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — technical ones\n wait for the next phase. When every concern is TECHNICAL, write\n no `## Open Questions` section. STRICT: answer it yourself only\n when the product default is unmistakable; a wrong intent costs a\n whole rebuild lap.\n\n ### Fold in the sketch\n\n - Fold everything the entry commit added under the concern it\n belongs to — a scratch note and a real code edit are both\n just a sketch to finish, never work to preserve; `unwind`\n already reverted both, so there is nothing left to delete\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n - `.gtd/REQUIREMENTS.md` already holds concerns with no\n ticked-but-unfolded answer waiting — a completed REVIEW round\n put this here, not a question you asked. Develop those\n concerns further with whatever the round raised; never\n rediscover or regroup them cold, the way the first lap does\n - The human's review-round edit was reverted the same way the\n entry commit was — read it from history:\n `git show <%= it.reviewBase %>`. (On the first lap that hash\n is the process's own diff base, `<%= it.startCommit %>` — this\n branch doesn't apply)\n - Every decision under `## Answered Questions` stays settled —\n never re-open one. A genuinely open PRODUCT point may still\n raise a fresh `## Open Questions` entry: the product gate sits\n on this path exactly as on the first lap\n - Before grouping anything, run `<%= it.vars.testCommand %>`\n yourself — a loop-back runs no green-baseline gate, so the\n tree may already be red (reverting the edit can undo a fix it\n made). If red, make that breakage the first concern, ahead of\n everything else — every later concern's green-on-its-own\n property assumes the suite started green\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n Answering here closes a gap between what you want the product to\n do and what gets built; changing nothing and re-running says that\n gap is already closed.\n\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered — with one\n escape: change nothing and re-run to advance with the questions\n unanswered.\n\n You can also leave a footnote alongside an answer — it never\n substitutes for ticking a box, which is still required before\n stepping is allowed:\n\n <%~ it.vars.footnoteRules %>\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 # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n skills: <%= it.vars.architectureSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state files this turn touches are\n `.gtd/ARCHITECTURE.md` (write it) and `.gtd/REQUIREMENTS.md`\n (delete once folded in) — no other files for notes or output\n - You do not resume the design conversation — a separate\n machine, its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full; treat every decision there, PRODUCT or\n TECHNICAL alike, as settled — never re-open it\n - Cold means no memory of triage's own back-and-forth, not no\n git access. This process started at commit\n `<%= it.startCommit %>`. Find the first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` it to see what started this process — a\n hand-edit, a scratch note, or both\n - Once `.gtd/ARCHITECTURE.md` is written, delete\n `.gtd/REQUIREMENTS.md` — folded in, it must not linger. Leave\n everything uncommitted and finish\n\n ## First lap\n\n - Develop `.gtd/ARCHITECTURE.md` from those concerns: for each,\n in order, work out the *how*, building on the settled *what*\n - You now know the *how*, so you know each concern's file footprint\n — list each concern's primary paths. Merge concerns whose\n footprints center on the same files into one, unless the later\n one only consumes an interface the earlier one creates (keeps a\n build-on-top sequence from collapsing into one blob). This\n authority is to merge only, never to split — the whole problem\n is over-granularity\n - Record every merge under `## Merged Concerns`,\n carrying both merged requirements verbatim so spec review\n still covers each independently\n - A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human sees it when reviewing the plan, and spec review is the\n real safety net\n - Prefer fewer, larger packages — the smallest independently\n valuable change, not the smallest change that compiles\n - Every open point here is TECHNICAL — triage already resolved\n the product ones, one phase earlier. PERMISSIVE: answer it\n yourself unless you genuinely cannot defend a default; a wrong\n technical call is still caught at spec review\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n Answering here closes a gap between what you want built and how\n it actually gets built; changing nothing and re-running says\n that gap is already closed.\n\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered — with one\n escape: change nothing and re-run to advance with the questions\n unanswered.\n\n You can also leave a footnote alongside an answer — it never\n substitutes for ticking a box, which is still required before\n stepping is allowed:\n\n <%~ it.vars.footnoteRules %>\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 skills: <%= it.vars.decomposeSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n - The only state files this turn touches are the package files\n under `.gtd/packages/` and `.gtd/ARCHITECTURE.md` (deleted) —\n no other files for notes or output\n - Work from `.gtd/ARCHITECTURE.md` if you wrote it earlier this\n conversation, otherwise read it (the converged technical\n plan). It already lists an ordered set of concerns with every\n merge/split judgement made — this turn is a mechanical\n write-out, not a planning one. A `## Merged Concerns` heading\n there records those merges, never a concern of its own: write\n no package file for it\n - Write one package file per concern, in the settled order,\n under `.gtd/packages/` (e.g. `.gtd/packages/01-name.md`,\n `02-name.md`, ...), each carrying that concern's\n requirement(s) — both, independently, if merged — its\n independent tasks, and each task's acceptance criteria as\n `- [ ]` checkboxes and relevant paths\n - Do not merge or split concerns here — that judgement already\n happened; carry the settled grouping over verbatim. No\n package file may reference any other `.gtd/` file\n - Once written, delete `.gtd/ARCHITECTURE.md`. Leave everything\n uncommitted and finish\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: pre\n states:\n # The review pre-judge: three nouls over the reviewBase→worktree diff,\n # judged BEFORE the agent's own authoring lap. Never routes off\n # `routes:` directly — `matchRoute` (`src/PatternMachine.ts`) SKIPS a\n # row whose question has no landed answer, so a bare\n # conjunction-by-inversion table here would let a PARTIAL verdict\n # (fewer than all three questions answered) fall through every escape\n # row straight to the catch-all fast path, the exact hole\n # `specReview.pre`'s own comment documents for its padded-slot design.\n # Unconditionally hands off to `preCheck` instead, which recomputes\n # the whole decision fresh from the landed `Gtd-Judge:` trailers —\n # the same recompute-from-scratch shape `packages.item.spec.scoping`\n # uses for the identical reason.\n pre:\n actor: judge\n label: Judging whether this round needs a full review lap\n message: |\n Judging whether this round of changes is mechanical, touches no\n public surface, and changes no behavior — confident enough on all\n three to skip the agent's own review lap and land straight at the\n human stop with a machine-written summary. Run `gtd judge answer`\n and pipe a verdict for `mechanicalOnly`, `touchesPublicAPI`, and\n `changesBehavior` — or land untouched to run the full lap (the\n conservative default; a skipped judgment never suppresses it).\n judge: |-\n <% const diffText = it.diff(it.reviewBase) %>{\"state\": {\"reviewBase\": \"<%= it.reviewBase %>\", \"diff\": <%~ JSON.stringify(diffText) %>}, \"questions\": [\n {\"id\": \"mechanicalOnly\", \"primitive\": \"noul\", \"instructions\": \"Over state.diff (the change from reviewBase to the working tree), is every hunk mechanical — formatting, a rename, generated output, or a straight refactor with no behavior change?\", \"criteria\": \"Answer no if any hunk could plausibly change what the code does, even slightly.\"},\n {\"id\": \"touchesPublicAPI\", \"primitive\": \"noul\", \"instructions\": \"Over the same state.diff, does any hunk touch a publicly exported symbol, CLI flag, config key, or documented file format?\", \"criteria\": \"Answer yes if uncertain which surface counts as public.\"},\n {\"id\": \"changesBehavior\", \"primitive\": \"noul\", \"instructions\": \"Over the same state.diff, does any hunk change runtime behavior visible to a user or a test?\", \"criteria\": \"Answer yes if uncertain.\"}\n ]}\n on:\n \"C\": preCheck\n \"* **\": preCheck\n # Recomputes the fast-path decision fresh from the just-landed\n # `Gtd-Judge:` trailers — never trusts a verdict to be complete.\n # Requires ALL THREE questions answered as `mechanicalOnly: yes`,\n # `touchesPublicAPI: no`, `changesBehavior: no`, each at\n # `reviewFastPath` confidence or better; a missing question, a\n # malformed answer, a wrong answer, or a low-confidence right answer\n # ALL fail the SAME way — nothing written, a clean tree, `\"C\"` routes\n # to `reviewing`. Only a fully-confident, fully-answered \"yes to\n # mechanical, no to the other two\" verdict writes the marker that\n # routes to `fastReview`.\n preCheck:\n actor: check\n label: Scoping the fast path\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/REVIEW_FAST.md\n threshold=<%~ it.vars.reviewFastPath %>; trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n fast=1\n for spec in mechanicalOnly:yes touchesPublicAPI:no changesBehavior:no; do\n id=${spec%%:*}\n want=${spec#*:}\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"$id\\\"\" | head -n 1)\n if [ -z \"$line\" ]; then\n fast=0\n continue\n fi\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n if [ \"$answer\" != \"$want\" ]; then\n fast=0\n continue\n fi\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n if [ -z \"$threshold\" ] \\\n || ! awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p>=t)}' 2>/dev/null; then\n fast=0\n fi\n done\n if [ \"$fast\" -eq 1 ]; then\n : > .gtd/REVIEW_FAST.md\n fi\n on:\n \"A .gtd/REVIEW_FAST.md\": fastReview\n \"C\": reviewing\n # The fast path: a `check`-actor script writes `.gtd/REVIEW.md` itself\n # — the pre-judge already established the round is mechanical,\n # touches no public surface, and changes no behavior, so there is\n # nothing for the agent's own `reviewing` lap to add. The human stop\n # is never skipped: this still routes to `await-review`. An empty\n # diff (a fast-pathed round with nothing to point at — reachable from\n # a clean-tree `--entry review-gate.check`) still needs at least one\n # pointer: `.gtd/REVIEW.md` itself is the one path this script can\n # always point at, since `gtd check review`'s own parser refuses a\n # chunk with none.\n fastReview:\n actor: check\n label: Writing the review summary\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/REVIEW_FAST.md\n {\n printf '# Review: %s\\n\\n' \"$(git rev-parse --short HEAD)\"\n printf '<!-- base: <%= it.reviewBase %> -->\\n\\n'\n printf '## Changes\\n\\n'\n files=$(git diff --name-only <%= it.reviewBase %>)\n if [ -n \"$files\" ]; then\n printf '%s\\n' \"$files\" | while IFS= read -r f; do\n printf -- '- [ ] ./%s — changed\\n' \"$f\"\n done\n else\n printf -- '- [ ] ./.gtd/REVIEW.md — no file changes this round\\n'\n fi\n } > .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW.md\": await-review\n \"M .gtd/REVIEW.md\": await-review\n \"* **\": await-review\n \"C\": await-review\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n skills: <%= it.vars.reviewSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is `.gtd/REVIEW.md` —\n no other files for notes or output\n\n Write `.gtd/REVIEW.md` in this exact format, to help a human\n review the changes:\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\n semantically (same feature/refactor/fix, across files), each\n with a short explanation of what changed and why, then one\n pointer per hunk (`./`-relative path, optional `#line`;\n checkboxes are for the human, not you). Put the note's\n opening line right on the pointer's line:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n Continue a longer note below the pointer, indented exactly two spaces\n — never four or more, which reads as a code block and never\n reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note sitting entirely on the line(s) beneath the pointer is\n also valid. Either way, the note must never start with a bare `./path` token\n — that parses as a second pointer, not a note\n No diff is given — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns\n plus anything pending); on a feedback round that's the previous\n review's boundary, so it covers only what's new.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off. Ticks are read-progress only: landing clears\n every box back to `- [ ]` on disk and nothing records which hunks\n you read — there is no persisted trail of it.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, a footnote anchored to a hunk, or a\n direct code edit — to send a FULL development lap\n (**review.deciding** → **review.triage** → **review.triaging**\n → **review.collecting** → re-triage; a hand-edit outside\n `.gtd/` skips straight from **review.deciding** to\n **review.collecting**, no verdict of your own required). A\n 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 A footnote works the same way here as a line note:\n\n <%~ it.vars.footnoteRules %>\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note or hand-edited any\n # file this round outside .gtd/; otherwise a clean sign-off. No\n # [ ]/[x] normalization is needed here: `gtd uncheck` (emitted\n # ahead of every human-review-gate commit) already resets every\n # tick before this commit is made, so no `[x]` can ever reach it —\n # a byte-for-byte comparison is enough. This turn only\n # CAPTURES the raw material into REVIEW_RAW.md — collecting judges\n # actionability. A/M REVIEW_RAW.md rows are declared before D\n # REVIEW.md so a feedback round (which also deletes REVIEW.md)\n # isn't mistaken for sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # The one case that leaves a clean tree below is REVIEW.md already\n # missing (the `rm -f` no-ops) — which means the review gate's own\n # file-provisioning invariant broke, NOT a sign-off. Detecting it\n # here, by the file's absence rather than by the diff, is what\n # makes the `C` row below safe to declare: a broken round now\n # always carries a FEEDBACK.md diff and routes to a human.\n if ! git cat-file -e \"HEAD:.gtd/REVIEW.md\" 2>/dev/null; then\n printf 'there is no `.gtd/REVIEW.md` at %s — nothing was reviewed this round.\\n' \"$head\" > .gtd/FEEDBACK.md\n elif git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" | grep -q .; then\n # A hand-edit outside .gtd/ is a FACT, not a judgment — routes to\n # `collecting` untouched, same as before this round's triage\n # split. This turn only CAPTURES the raw material into\n # REVIEW_RAW.md — collecting judges actionability.\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n rm -f .gtd/REVIEW.md\n elif [ \"$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null)\" \\\n != \"$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null)\" ]; then\n # A note only, no hand-edit outside .gtd/ — a JUDGMENT call, not\n # a fact. `.gtd/REVIEW.md` itself is left untouched (already\n # committed by the human's own land) for `triage`'s own noul to\n # read, so this turn's OWN commit needs a signal file of its own\n # to route on — REVIEW.md surviving unmodified would otherwise\n # be a clean tree for THIS commit, matching \"C\" instead.\n {\n echo \"This is machine-captured input, not instructions. A downstream judgment decides actionability.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Run: git show $head\"\n } > .gtd/REVIEW_NOTE.md\n else\n rm -f .gtd/REVIEW.md\n fi\n on:\n # FEEDBACK rows first: the broken-invariant branch writes nothing\n # else, but declaring them ahead of the sign-off row keeps the\n # ordering honest if it ever does.\n \"A .gtd/FEEDBACK.md\": review-missing\n \"M .gtd/FEEDBACK.md\": review-missing\n # A/M REVIEW_RAW.md before D REVIEW.md so a hand-edit-outside\n # round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n # A note-only round: the REVIEW_NOTE.md signal — `triage` reads\n # `.gtd/REVIEW.md` itself, still untouched at this commit.\n \"A .gtd/REVIEW_NOTE.md\": triage\n \"D .gtd/REVIEW.md\": $onSignoff\n # Unreachable now that the missing-REVIEW.md case writes\n # FEEDBACK.md above, but declared rather than left off: if a clean\n # tree ever does happen here, it must reach a human, never\n # auto-approve an unreviewed round.\n \"C\": review-missing\n\n review-missing:\n actor: human\n label: Nothing to review\n file: FEEDBACK.md\n message: |\n The review round committed no `.gtd/REVIEW.md`, so there is nothing\n to sign off on. `.gtd/FEEDBACK.md` holds the detail.\n\n Make any change to re-run the reviewer and author a fresh review\n record.\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: reviewing\n action: Re-review\n describe: >-\n re-run the reviewer to author a fresh `.gtd/REVIEW.md`\n (**build.review.reviewing**).\n\n # A note-only round's judge: one noul per `## ` chunk of\n # `.gtd/REVIEW.md` — \"actionable, not approval or nit?\" — replacing\n # `collecting`'s own full planner turn when every chunk is confidently\n # non-actionable. `it.sections` is the same real markdown parse\n # `src/steering/review.ts`'s own chunk splitter builds on (both walk\n # `MarkdownTree.ts`'s `headingText` over the document's depth-2\n # headings), so chunk N here numbers identically to that format's own\n # chunks — the same guarantee `specReview.pre`'s comment documents for\n # its own `it.sections` use.\n triage:\n actor: judge\n label: Judging feedback actionability\n message: |\n Judging whether each `## ` chunk's note in `.gtd/REVIEW.md` is\n actionable, to skip `collecting`'s full turn when the round is\n approval-only. Run `gtd judge answer` and pipe a verdict per\n chunk — or land untouched to run the full triage (the\n conservative default; a skipped judgment never signs off).\n judge: |-\n <%\n const chunks = it.sections(\".gtd/REVIEW.md\")\n const questions = chunks.map((title, i) => ({\n id: `chunk-${i + 1}`,\n primitive: \"noul\",\n instructions: `Is the note under review chunk \"${title}\" (in .gtd/REVIEW.md) actionable — anything beyond an approving remark with no code edit?`,\n criteria: \"A concrete request, a question, a code comment, or a hand-edit under this chunk answers yes. No note, or a purely approving remark, answers no.\",\n }))\n %>{\"state\": <%~ JSON.stringify({ review: it.read(\".gtd/REVIEW.md\") }) %>, \"questions\": <%~ JSON.stringify(questions) %>}\n on:\n \"C\": triaging\n \"* **\": triaging\n # Recomputes actionability fresh from the just-landed `Gtd-Judge:`\n # trailers and `.gtd/REVIEW.md`'s own chunk count — never trusts\n # `triage`'s verdict to be complete. An unanswered chunk (a skipped\n # judgment, a partial verdict, or a dirty land) defaults to\n # ACTIONABLE — the conservative direction, matching `triage`'s own\n # message (\"land untouched to run the full triage\"). Its `awk`\n # heading scan must number chunks the same way `it.sections` did for\n # `triage`'s own `chunk-N` ids — same constraint `scoping`'s comment\n # documents for `specReview`.\n triaging:\n actor: check\n label: Filtering non-actionable feedback\n script: |\n #!/usr/bin/env sh\n set +e\n threshold=<%~ it.vars.reviewNoteActionable %>; head=$(git rev-parse HEAD)\n trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n # `it.sections`'s real mdast parse (CommonMark) numbered `chunk-N`\n # against every TOP-LEVEL depth-2 heading — never one absorbed as\n # a list item's own lazy continuation. A chunk's own pointer lines\n # are always `- ` list items (2-space content column), so a `##`\n # indented 2-3 spaces right after one stays absorbed into that\n # list under BOTH parsers — a bare `/^## /` scan is correct there,\n # and widening it would instead miscount a note's own continuation\n # line that happens to start with `##` as informal markdown. Only\n # a SINGLE leading space unconditionally breaks a `- ` list's\n # continuation and becomes a real top-level heading either way,\n # regardless of what precedes it — the one indent depth `/^## /`\n # alone would miss.\n total=$(awk '\n /^```/ { f = !f; next }\n f { next }\n /^ ?## / { c++ }\n END { print c + 0 }\n ' .gtd/REVIEW.md 2>/dev/null)\n [ -n \"$total\" ] || total=0\n actionable=0\n i=1\n while [ \"$i\" -le \"$total\" ]; do\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"chunk-$i\\\"\" | head -n 1)\n # Missing entirely (a skipped judgment, or a partial verdict\n # that never answered this chunk) defaults to actionable — the\n # one default direction this gate must never get wrong.\n this_one=1\n if [ -n \"$line\" ]; then\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n if [ \"$answer\" = \"no\" ]; then\n this_one=0\n elif [ \"$answer\" = \"yes\" ]; then\n this_one=1\n if [ -n \"$threshold\" ] \\\n && awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p<t)}' 2>/dev/null; then\n this_one=0\n fi\n fi\n fi\n [ \"$this_one\" -eq 1 ] && actionable=1\n i=$((i + 1))\n done\n [ \"$total\" -eq 0 ] && actionable=1\n if [ \"$actionable\" -eq 1 ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n rm -f .gtd/REVIEW.md .gtd/REVIEW_NOTE.md\n else\n rm -f .gtd/REVIEW.md .gtd/REVIEW_NOTE.md\n fi\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n \"C\": collecting\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are judging and classifying a round of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state files this turn touches are\n `.gtd/REQUIREMENTS.md` and `.gtd/REVIEW_RAW.md` (deleted) —\n you classify, you do not build\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. Work from what you already reviewed if you\n wrote today's review earlier this conversation; otherwise read\n that commit's diff yourself first.\n\n The round is actionable if any of these hold:\n\n - The human left a note on `.gtd/REVIEW.md`. A note is a mandatory\n concern below\n - The human added a code comment this round, even a plain-prose\n one — describe it as a concern, and note the comment line\n itself is transient: it must not survive the lap that\n satisfies it\n - The human hand-edited non-comment code this round — no longer\n a committed intent to build on, but a sketch like the entry\n commit's own diff. Describe what it was reaching for; expect\n the next lap to re-derive it from scratch, never call it final\n\n Not actionable only when none of the above holds — nothing but an\n approving remark, no code edit, no substantive note. Never invent\n actionability, and never dismiss a real note or edit as approval.\n\n - If actionable: write `.gtd/REQUIREMENTS.md` with an ordered\n list of concerns, each PRODUCT or TECHNICAL — the shape\n `design.triage` builds, one `## <heading>` per concern in\n build order. Fold every note, comment, and hand-edit in under\n its concern. Raise no open questions here — `design.triage`\n owns that later. Then delete `.gtd/REVIEW_RAW.md` and finish\n - If not: delete `.gtd/REVIEW_RAW.md` and finish, writing\n nothing to `.gtd/REQUIREMENTS.md` — that alone is the sign-off\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: pre\n states:\n # One noul per `## ` section of the package file, padded to a FIXED\n # 8-slot id set (section-1..section-8): a missing real section at a\n # slot still renders — never omits — that slot's question, so the\n # compiler's own load-time stub check (`judgeQuestionIds`) sees the\n # same 8 ids on both its stub renders regardless of real content. A\n # package with more than 8 real sections fails open on every slot (the\n # `overflow` branch below) — full review, never a silent partial one.\n #\n # Deliberately carries NO `routes:` — a verdict answering only SOME of\n # the 8 slots (the expected shape: real slots plus whatever padding a\n # driver bothers to answer) must never let an unanswered REAL section\n # slip through as an implicit approval, and `routes:`'s own\n # first-match-wins matching (`matchRoute`, `src/PatternMachine.ts`) has\n # no way to require \"every question was answered\" — an absent answer\n # just falls through every row untouched, unable to tell \"this is\n # padding\" from \"the driver skipped a real requirement\". `scoping`\n # below owns the whole approve/scope decision instead, recomputing\n # fresh from the just-landed `Gtd-Judge:` trailers AND the package's\n # own real section count every time — an unanswered real section\n # defaults to failing there, closing the hole structurally rather than\n # trusting every future verdict to be complete.\n pre:\n actor: judge\n label: Judging spec coverage\n message: |\n Judging whether the code already satisfies each requirement in the\n package spec, before spending a full review turn. Run `gtd judge\n answer` and pipe a verdict per section — or land untouched to run\n the full review (the conservative default; a skipped judgment\n never suppresses anything).\n judge: |-\n <%\n const pkgPath = it.read(\".gtd/NEXT.md\").trim()\n const pkg = it.read(pkgPath)\n const sections = it.sections(pkgPath)\n const MAX_SECTIONS = 8\n // More real sections than slots: fail open on EVERY slot rather\n // than silently judging only the first 8 and letting the rest\n // through unreviewed — the one direction this gate must never\n // fail in (see the review feedback this fixed).\n const overflow = sections.length > MAX_SECTIONS\n const questions = []\n for (let i = 0; i < MAX_SECTIONS; i += 1) {\n const title = sections[i]\n if (overflow) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"This package has more `## ` sections than this gate can judge (max 8) — always answer no, never yes: a confident approval here would silently skip review of the sections beyond the eighth.\",\n criteria: \"Structural, not a judgment call — no is the only correct answer.\",\n })\n } else if (title !== undefined) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: `Is the requirement \"${title}\" already fully satisfied by the code on the range from ${it.startCommit} to the working tree?`,\n criteria: \"Judge from the package markdown plus that range, read yourself. Only answer yes at a probability clearing the threshold below if genuinely confident nothing in this section is missing.\",\n })\n } else if (sections.length === 0) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"This package has no `## ` sections at all — there is nothing to judge. Always answer no, never yes: a confident approval here would silently skip the only review this package would ever get.\",\n criteria: \"Structural, not a judgment call — no is the only correct answer.\",\n })\n } else {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"Padding slot: this package has fewer than 8 `## ` sections. There is no corresponding requirement.\",\n criteria: \"Always answer yes at p 1 — nothing to evaluate.\",\n })\n }\n }\n %>{\"state\": <%~ JSON.stringify({ package: pkg }) %>, \"questions\": <%~ JSON.stringify(questions) %>, \"specPreJudgeThreshold\": \"<%= it.vars.specPreJudge %>\"}\n on:\n \"C\": scoping\n # A dirty-tree land with no verdict piped — package 01's own\n # `build.health.judge`/`packages.item.health.judge` fix for this\n # same shape: the skipped-judgment fallback must not stall on a\n # dirty tree either, so it takes the same conservative target \"C\"\n # does — `scoping` recomputes the real decision fresh either way.\n \"* **\": scoping\n # Owns the WHOLE approve/scope decision `pre`'s own comment describes:\n # recomputes fresh from the package's real `## ` sections (never trusts\n # `pre`'s verdict to be complete) and the just-landed `Gtd-Judge:`\n # trailers. An unanswered real section — a partial verdict, a skipped\n # judgment, or a dirty-tree land with no verdict at all — defaults to\n # FAILING: only `.gtd/SPEC_CLEARED.md` (written exclusively when every\n # real section is both answered and confident) approves; every other\n # outcome, including a script bug that produces no output at all,\n # lands on `review` — the fail-open direction can never accidentally\n # become fail-approve.\n #\n # The `awk '/^```/{f=!f} !f && /^## /{...}'` heading scan below MUST\n # number sections the same way `it.sections` (an mdast parse,\n # `src/steering/MarkdownTree.ts`'s `headingSections`) numbered them for\n # `pre`'s own `section-N` ids, or a verdict answered against mdast's\n # numbering strikes/scopes the WRONG section here — a real, silent\n # corruption a round of review caught (a `##` line quoted inside a\n # fenced code block is not a heading; shell can't run mdast, so this\n # skips fenced regions by hand instead, the one desync case realistic\n # here). A setext (`---`-underlined) H2 is still a mismatch — no\n # package/finding content in this codebase's own history has ever used\n # one; `docs/`'s own style never does either.\n scoping:\n actor: check\n label: Scoping the review to the failing sections\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/SPEC_SCOPE.md .gtd/SPEC_CLEARED.md\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n threshold=<%~ it.vars.specPreJudge %>; if [ -n \"$pkg\" ] && [ -f \"$pkg\" ]; then\n titles=$(awk '/^```/{f=!f} !f && /^## /{sub(/^## /,\"\"); print}' \"$pkg\")\n total=0\n [ -n \"$titles\" ] && total=$(printf '%s\\n' \"$titles\" | wc -l | tr -d ' ')\n if [ \"$total\" -gt 0 ]; then\n trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n i=1\n while [ \"$i\" -le \"$total\" ]; do\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"section-$i\\\"\" | head -n 1)\n # Missing entirely (padding, a skipped judgment, or a real\n # section the verdict just never answered) defaults to\n # failing — the one default direction this gate must never\n # get wrong.\n failing=1\n if [ -n \"$line\" ]; then\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n # A noul answer is conventionally a JSON boolean\n # (`true`/`false`), never the bare \"yes\"/\"no\" `routes:`\n # matching normalizes it to internally (`asRouteAnswers`,\n # src/step/planStep.ts), but the decode accepts a quoted\n # string too — the committed trailer carries the RAW\n # verdict, so both spellings must clear a section here, or\n # a driver using the string form silently loses the whole\n # optimisation, scoping every section into review forever\n # without ever being wrong.\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n # `[ -n \"$threshold\" ]` guards a BLANK `specPreJudge`: awk\n # treats an empty `-v t=` as the uninitialized strnum `0`,\n # so `p >= t` would be true at ANY probability — turning\n # the workflow's documented \"blank disables\" convention\n # into fail-APPROVE for this one gate. Blank must instead\n # never clear anything, the same failing default as a\n # missing answer.\n if [ \"$answer\" = \"yes\" ] && [ -n \"$threshold\" ] \\\n && awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p>=t)}' 2>/dev/null; then\n failing=0\n fi\n fi\n if [ \"$failing\" -eq 1 ]; then\n title=$(printf '%s\\n' \"$titles\" | sed -n \"${i}p\")\n [ -n \"$title\" ] && printf -- '- %s\\n' \"$title\" >> .gtd/SPEC_SCOPE.md\n fi\n i=$((i + 1))\n done\n [ -f .gtd/SPEC_SCOPE.md ] || : > .gtd/SPEC_CLEARED.md\n fi\n fi\n on:\n \"A .gtd/SPEC_CLEARED.md\": $onApproved\n \"A .gtd/SPEC_SCOPE.md\": review\n \"* **\": review\n \"C\": review\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n skills: <%= it.vars.specReviewSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are reviewing a freshly-built work package against its own\n spec.\n\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md` — write it only when you find problems\n - The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n <% let scope; try { scope = it.read(\".gtd/SPEC_SCOPE.md\") } catch (e) { scope = undefined } %><% if (scope) { %>\n - A pre-judge already found the other sections satisfied. Confine\n your review to only these sections: <%~ scope %>\n <% } %>\n - Verify the implementation against it: tasks done, criteria\n met, code sound and consistent with the codebase. No diff is\n given — read the range yourself, from `<%= it.startCommit %>`\n to the working tree, process-wide (it can span earlier\n packages)\n - You own that bar; nothing downstream re-weighs your findings\n - Write nothing when the package fully satisfies its spec —\n silence is your approval. Otherwise write\n `.gtd/SPEC_FEEDBACK.md` listing what would violate the spec if\n it shipped unaddressed, specific enough to act on, each as its\n own `## ` heading\n - Never fix anything yourself and never delete the package\n file — a later step owns that\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now) and\n # any REVIEW_RAW.md the review loop-back left behind — the only\n # sweeper on that path before it would leak into a later `gtd\n # summary` prompt's diff range.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n skills: <%= it.vars.buildSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn may write is `.gtd/SATISFIED.md`;\n never delete the package file (the spec-review gate reads it\n after you) or touch `.gtd/NEXT.md` — `picking` owns it\n - The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n - First check its acceptance criteria against the current tree —\n an earlier fix turn may already satisfy them. If **every**\n criterion is met, implement nothing: write `.gtd/SATISFIED.md`\n with each criterion's concrete evidence (commit, file, or\n symbol), change nothing else, and finish. Otherwise implement\n normally and skip that file\n - Implement every task the package describes, no more, no less,\n fanning independent ones out to parallel subagents where your\n harness supports it; leave other package files untouched\n - Leave the package file in place, everything uncommitted, then\n finish your turn\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n skills: <%= it.vars.fixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n - If the only way to green the suite is another package's work,\n make the smallest change that gets there — that package can\n then legitimately report itself already satisfied later\n - Leave everything uncommitted and finish your turn — do not commit\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n skills: <%= it.vars.reviewFixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md` — address it, then delete it\n - Read it (the reviewer's concerns) and the package spec\n (`<%= it.read(\".gtd/NEXT.md\") %>`), then fix the code to\n resolve every concern\n - Delete `.gtd/SPEC_FEEDBACK.md` once resolved; leave everything\n else uncommitted and finish your turn\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/SPEC_SCOPE.md .gtd/SPEC_CLEARED.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n # Nothing left to sweep (an already-clean NEXT.md/package) still\n # needs to proceed to the next package, not stall here.\n \"C\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n skills: <%= it.vars.fixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n - Leave everything uncommitted — do not commit\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Hoisted here, 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.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\" 2> .gtd/.unwind-error\n code=$?\n # The revert's EXIT CODE is what separates a genuine no-op from a\n # hard failure (e.g. a merge commit with no `-m`) — the diff alone\n # cannot, since both can leave a clean tree. Turning the failure\n # into a FEEDBACK.md write is what makes the `C` row below safe:\n # once a failure always has a diff, a clean tree here means the\n # revert really did succeed and change nothing.\n if [ \"$code\" -ne 0 ]; then\n printf 'gtd could not unwind %s out of your working tree.\\n\\n' \"$commit\" > .gtd/FEEDBACK.md\n if [ -s .gtd/.unwind-error ]; then\n cat .gtd/.unwind-error >> .gtd/FEEDBACK.md\n else\n printf '`git revert --no-commit` exited %s and produced no output.\\n' \"$code\" >> .gtd/FEEDBACK.md\n fi\n fi\n rm -f .gtd/.unwind-error\n on:\n \"A .gtd/FEEDBACK.md\": unwind-failed\n \"M .gtd/FEEDBACK.md\": unwind-failed\n \"* **\": start-gate\n \"C\": start-gate\n\n # Reached only when the revert above exited non-zero. A human repairs\n # the tree by hand; start-gate.check clears FEEDBACK.md on its way\n # green, so nothing has to sweep it here.\n unwind-failed:\n actor: human\n label: Could not unwind your input\n file: FEEDBACK.md\n message: |\n gtd could not revert your sketch out of the working tree.\n `.gtd/FEEDBACK.md` holds the error.\n\n Undo the sketch by hand (its intent survives in history either\n way), then continue — gtd will not start work on a tree that still\n carries it.\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: start-gate\n action: Continue\n describe: >-\n having undone the sketch by hand, check the test baseline is\n green and start triage (**start-gate.check**).\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, 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.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture-pre\n\n # Judges whether `.gtd/REQUIREMENTS.md` — the committed artifact\n # `design.triage` just settled — warrants a dedicated architecture\n # pass, never an ex-ante complexity score off the bare `.gtd/TODO.md`\n # (that has no committed artifact to judge, and would break the\n # evidence rule every other judge state in this file follows). A\n # single fixed noul, so `routes:` is the right tool here.\n architecture-pre:\n actor: judge\n label: Judging whether this plan warrants an architecture pass\n message: |\n Judging whether `.gtd/REQUIREMENTS.md`'s settled concerns need a\n dedicated architecture pass — real structural decisions, multiple\n integration points, or a non-obvious tradeoff — before packages\n are written. Run `gtd judge answer` and pipe a verdict for\n `architectureWarranted` — or land untouched to run the full pass\n (the conservative default; a skipped judgment never suppresses\n it).\n judge: |-\n {\"state\": {\"requirements\": <%~ JSON.stringify(it.read(\".gtd/REQUIREMENTS.md\")) %>}, \"questions\": [\n {\"id\": \"architectureWarranted\", \"primitive\": \"noul\", \"instructions\": \"Given the settled concerns in `.gtd/REQUIREMENTS.md` (in state), does this plan warrant a dedicated architecture pass — real structural decisions, multiple integration points, or a non-obvious tradeoff — before packages are written?\", \"criteria\": \"Answer yes if uncertain; a trivial, single-concern, mechanical plan with no real design decision answers no.\"}\n ]}\n routes:\n - question: architectureWarranted\n is: \"no\"\n minP: <%~ it.vars.architectureSkipMinP %>\n to: architecture-promote\n - to: architecture\n on:\n \"C\": architecture\n \"* **\": architecture\n\n # The skip path: promotes `.gtd/REQUIREMENTS.md` wholesale into a\n # SINGLE `.gtd/packages/01-<slug>.md` before the queue is entered —\n # never split, `architecture.decompose`'s own job, skipped here.\n # Without this, skipping `architecture.decompose` would leave\n # `.gtd/packages/` empty, so `packageLoop.picking` would match its\n # `\"C\"` row, route `$onDrained`, and the process would close having\n # built nothing.\n architecture-promote:\n actor: check\n label: Promoting the plan straight to a package\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd/packages\n title=$(awk '/^```/{f=!f} !f && /^## /{sub(/^## /,\"\"); print; exit}' .gtd/REQUIREMENTS.md)\n [ -z \"$title\" ] && title=package\n slug=$(printf '%s' \"$title\" | tr '[:upper:]' '[:lower:]' \\\n | sed 's/[^a-z0-9]\\{1,\\}/-/g; s/^-*//; s/-*$//')\n [ -z \"$slug\" ] && slug=package\n mv .gtd/REQUIREMENTS.md \".gtd/packages/01-${slug}.md\"\n on:\n \"* **\": packages\n # Unreachable in practice (`mv` always changes the tree) but\n # declared rather than left off: if a clean tree ever does happen\n # here, it must run the full pass, never silently produce nothing.\n \"C\": architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
54309
+ var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # `healthGate.judge`'s probability floor for the \"identical\" verdict to end\n # a retry loop early — the ONLY off-switch this judged gate has (see\n # 01-judgment-surface.md's Requirement section): a repo retunes or disables\n # it by repointing this var, never by editing the bundled routes: row.\n judgeIdenticalMinP: \"0.7\"\n # `specReview.pre`'s per-section noul: a section answered \"yes\" needs at\n # least this confidence to skip `review` for it; \"no\", or \"yes\" under this\n # floor, both scope the review to that section instead. UNMEASURED: no\n # mined history of \"requirement already satisfied?\" judgments exists to\n # derive it from, and no arithmetic on another threshold can stand in for\n # one — a round of review caught an earlier version deriving this from a\n # value measured for a different question entirely. Chosen conservatively\n # high instead: the pre-judge should rarely fire until real pre-judge\n # history exists to mine a measured value from. Every judged-gate\n # threshold in this file is now an unmeasured conservative default on the\n # same footing.\n specPreJudge: \"0.9\"\n # `humanReview.pre`'s three nouls (mechanicalOnly, touchesPublicAPI,\n # changesBehavior) over `git diff <reviewBase>` — all three must clear this\n # floor at \"yes\"/\"no\"/\"no\" respectively to take the fast path\n # (`fastReview`, skipping the agent's own authoring lap). Unmeasured — no\n # mined history judges this three-way combination — so chosen\n # conservatively high, the same\n # unmeasured-default posture `specPreJudge` documents, until real\n # `humanReview.pre` history exists to mine a value from.\n reviewFastPath: \"0.9\"\n # `humanReview.triage`'s per-chunk noul over `.gtd/REVIEW.md`'s own\n # chunks: \"actionable, not approval or nit?\" A chunk answered \"yes\" below\n # this floor is treated as non-actionable (folded into sign-off) rather\n # than spent on a `collecting` turn — a chunk answered \"no\" is\n # non-actionable regardless of this floor. Blanking it disables the\n # dismissal (every \"yes\" counts, at any confidence) — the same\n # blank-turns-this-judged-gate-off convention `judgeIdenticalMinP` and\n # `specPreJudge` follow, kept in the SAFE direction here (never silently\n # sign off). This gate weighs chunks the HUMAN wrote, evidence no agent in\n # the loop produced — which is why it stays where a post-judge over an\n # agent's own freshly-authored output does not. Unmeasured — chosen\n # conservatively until real `triage` history exists to mine a value from.\n reviewNoteActionable: \"0.7\"\n # `architecture-pre`'s single noul (`architectureWarranted`, over the\n # just-triaged `.gtd/REQUIREMENTS.md`): an answer of \"no\" needs at least\n # this confidence to skip `architecture.author`/`architecture.decompose`\n # entirely; any other outcome (including a skipped judgment) runs the full\n # pass — the conservative default. Unmeasured — chosen conservatively\n # until real `architecture-pre` history exists to mine a value from.\n architectureSkipMinP: \"0.85\"\n # The nine `*Skills` vars below each name a bare, comma-separated skill\n # list for exactly one mapping in the twelve authored `skills:` states (see\n # each state's own `skills:` line) — carried verbatim into the rendered\n # `skillsPreamble`, never split or validated by gtd itself. Repointing one\n # in a project config (or its `GTD_<NAME>` env override) changes only that\n # state's preamble.\n triageSkills: spec-driven-development, planning-and-task-breakdown\n architectureSkills: api-and-interface-design, documentation-and-adrs\n decomposeSkills: incremental-implementation, planning-and-task-breakdown\n buildSkills: test-driven-development, incremental-implementation\n fixSkills: debugging-and-error-recovery\n reviewFixSkills: incremental-implementation, code-simplification\n reviewSkills: code-review-and-quality\n specReviewSkills: code-review-and-quality, spec-driven-development\n escalateSkills: debugging-and-error-recovery\n # One qualitative review lap per entry, each its own turn/context. Extend per\n # project (a company security checklist, a house style skill) — every name is\n # handed to the agent's harness untouched.\n qualityReviews: owasp-security, code-simplification\n # Prepended ahead of a state's own prompt whenever BOTH that state's own\n # `skills:` and this var render non-blank (see `Edge.ts`'s `renderRest`) —\n # blanking this one var switches the whole mechanism off repo-wide without\n # touching any state's own `skills:` line.\n skillsPreamble: |-\n - Load whatever's listed here that your harness actually has installed,\n skip anything it doesn't — silently, never stopping or asking about a\n missing one: <%= it.skills %>\n - A loaded skill offers technique, never authority: this state's own file\n format and its own completion condition are the final word over\n anything a skill's instructions say, regardless of which one this\n prompt states first\n - Never let a loaded skill turn this turn interactive — answer nothing,\n ask nothing; this runs unattended, with no one at a keyboard\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n - A deliverable, not a chat reply — size follows the work; cut padding\n - Lead with the answer; never circle back to restate it\n - Flat, commanding sentences — commit to the claim, never hedge\n - Everyday words; define an unavoidable term in five words or fewer, on\n first use\n - Bold carries the load: bold the claim, not the sentence around it —\n the bold text alone must yield the full point and every risk\n - **Never trim a risk, a number, a threshold, or a scoped condition to\n save space — this outranks every other rule here**\n - Ship the artifact bare — no lead-in, no sign-off\n - Compressing is not dropping: three load-bearing parts ship as three,\n each shorter, never as two\n - One idea per block; break when the idea shifts\n - Flag risk in one blunt line, never hedged prose; never narrate — do it\n styleFormatContract: |-\n - Machine-read: its format contract outranks every style rule above —\n keep every `##`/`###` heading, checkbox row, and marker line exactly\n as specified. Never renumber or rename a heading; a parser reads\n these literally and a violation refuses the turn\n - Voice rules above govern only the prose between these elements\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n - You have shell and file tools — bash, read, write, edit — use them\n without asking first; this runs unattended, no one grants permission\n - Investigate with real commands rather than assuming a file's, a\n commit's, or a decision's status — then act on what you find\n - No injected status block — no cwd, no branch, no history, no tree\n state — orient yourself first with `git status`, `git log`, `ls`\n - The turn's message names a commit, a range, or a file to go inspect\n yourself — never a diff or summary inlined into the conversation\n - Across turns one conversation may span, work from what you already\n read and settled, unless told this is a fresh, cold pass\n designPersona: |-\n You are the product-facing planning voice in gtd's build pipeline: turn\n a raw sketch — a hand-edit, a scratch note, or both — into an ordered,\n classified list of concerns, and hold the running product conversation\n with the human it depends on. You are read by that human — write\n plainly — not by a parser, except where a state says otherwise.\n architectPersona: |-\n You are the technical planning voice in gtd's build pipeline: take over\n once product concerns are settled, reading them cold, no carried\n conversation. Work out the *how* per concern — structure, data models,\n tech-stack choices, error handling — raise the technical open\n questions, then write one package spec per concern with no further\n judgement call: the grouping is already decided.\n reviewerPersona: |-\n You are the independent reviewing mind in gtd's build pipeline —\n deliberately separate from whoever wrote the code, with no attachment\n to it. Two turns: write a structured review document grouping a diff\n into chunks; later, classify a round of the human's feedback as\n actionable or just approving, never fixing anything yourself. You judge\n and classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in gtd's build\n pipeline, checking one freshly-built package against its spec. Verify\n only: tasks done, criteria met, code sound and consistent with the\n codebase. Write feedback only when something is genuinely wrong —\n otherwise write nothing; a clean turn IS the approval. Never fix what\n you find — naming it precisely enough for a fix turn is the whole job.\n builderPersona: |-\n You are the TDD implementer in gtd's build pipeline: build one\n package's declared scope end to end, tests first, and come back to fix\n things when redirected — a failing check, or reviewer feedback. Stay\n strictly inside the package in front of you; never touch another\n package's files or code outside the task. Treat feedback as the\n diagnosis to act on; discard it, with reason, only when simply wrong.\n finisherPersona: |-\n You are the closing identity in gtd's build pipeline — the last coder\n before it closes. Fix a late-breaking failing check after sign-off,\n focused and minimal. Every turn lands its own commit — nothing here\n gets squashed away.\n escalationPersona: |-\n You are the diagnosing voice in gtd's build pipeline, called in once a\n check has stayed red past repeated fix attempts. Read the failing\n output, the previous round's, and the code those attempts touched, then\n write down what is failing, why the earlier attempts didn't resolve it,\n and concrete approaches worth trying next. Never fix the code yourself —\n naming the problem precisely enough for the next fix turn is the whole\n job.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n - You are an autonomous coding agent\n - This workflow's own state files are its private scratchpad, never\n project code or documentation\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n - The goal is shared understanding, not a quota or an empty section: a\n question exists to close a gap between what the human wants and\n what the agent is about to build. Ask whenever that gap is open —\n even when the point looks cheap to undo, settled-looking, or\n narrow. This goal outranks what follows; the conditions below are\n signals a gap is real, strong evidence to ask, never permission\n withheld\n - Before writing `## Open Questions`, walk every concern you grouped and\n collect every point above the bar into that ONE section — a question\n held back for a later lap is a bug. One lap is the target; a second is\n the exception\n - Raise first, narrow second: only once every concern above the bar is\n raised into `## Open Questions`, walk that same set once more and\n answer the ones a confident default settles, moving each into\n `## Answered Questions` with its answer. This pass narrows what's\n already raised — it is never license to raise less, and skipping the\n raise to answer straight through is the same bug as holding a\n question back for a later lap\n - Treat each of these as strong evidence a gap is real: it's a genuine\n fork with divergent outcomes (user-visible for product, materially\n different builds for technical); the diff and history don't already\n settle it (treat an explicit `.gtd/TODO.md` directive, a committed\n hand-edit, or — on a loop-back lap — the human's own review-round\n edit/note as settled); or it would be expensive to undo once\n packages are written\n - Where no gap in shared understanding exists, decide it yourself:\n record it under `## Answered Questions` as\n `### <the point phrased as a question>`\n plus a one-line rationale in prose, no checkboxes. That heading\n always comes last, after every other `##` section\n - Above the bar, write it under `## Open Questions` — always the first\n `##` section — as `### <question>` plus a checkbox list: two\n concrete options and a free-text slot, all unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n - Never tick a box yourself — the human ticks exactly one per question\n questionBarReturn: |-\n - This lap continues the same goal as the first: close the gap\n between what the human wants and what gets built. Folding in\n answers and deciding what's left is that same goal continued, not\n a different job\n - The human answered — a ticked box, a free-text answer in place of\n `_your answer_`, a deleted question, or a deleted `## Open Questions`\n section are all answers. Fold each resolved answer into its concern's\n prose, then move the question into `## Answered Questions` as\n `### <question>` with the resolved answer in plain prose — no\n checkboxes\n - `## Answered Questions` is always the last `##` section — a moved\n question lands there, never wherever `## Open Questions` used to sit\n - Never re-raise a deleted question, and never re-open a settled\n `## Answered Questions` entry\n - An answer may earn a follow-up: if it opens a genuinely new fork above\n the bar — one the answer itself created — raise it as a fresh `##\n Open Questions` entry on this same lap. Never restate a question\n already asked, and never treat this as licence to re-open a question\n already settled under `## Answered Questions`\n - Recognise a silent lap from `## Open Questions` still present with\n nothing ticked and nothing else changed — the human's way of saying\n the gap is already closed. That lap ends the questions, whatever the\n goal says: decide every remaining question yourself, move each to\n `## Answered Questions` with a one-line rationale, raise nothing new,\n and leave no `## Open Questions` section behind\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n - The only state file this turn writes is `.gtd/FEEDBACK.md` — no other\n files for notes or output\n - Read `.gtd/FEEDBACK.md` (the failing test output) and fix the code so\n the suite passes\n - When `.gtd/ESCALATION.md` is present, it is a human-reviewed analysis\n of why earlier attempts failed — read it and treat it as the primary\n instruction for this turn. Never edit or delete it yourself, even once\n you believe you've resolved it: only a genuinely green check retires\n it, so an attempt that turns out to be wrong still leaves the next\n turn's instruction in place. Its absence is the ordinary case: an\n unremarkable red round with nothing to escalate yet\n - Delete `.gtd/FEEDBACK.md` either way — once the suite passes, or once\n you have established the feedback was wrong; leaving it in place is\n never the right end state, the next check writes its own\n\n # The human-facing half of footnotes — how to type one, injected into the\n # three human-gate messages (`design.gate.answer`, `architecture.gate.answer`,\n # `build.review.await-review`). `footnoteFoldIn` below is the agent-facing\n # half; the two never share text, different audience and content.\n footnoteRules: |-\n - Leave a footnote anywhere: mark the exact spot with `[^name]` (any\n name, no whitespace or `]`), then define it below as `[^name]:\n explain what you mean` — indent a longer comment's continuation\n lines. (The literal words \"your comment\" are this format's seeded\n placeholder body — a definition still holding them exactly is\n flagged as unfilled, so write your own words there)\n - A footnote is a comment on that exact spot — the hunk, line, or\n paragraph it marks — never a whole-file remark\n - `name` is yours to pick; only a definition's name must be unique in\n this document — the same name may mark more than one spot\n # The agent-facing half — injected into the three prompts that fold a\n # footnote in (`design.triage`, `architecture.author`,\n # `build.review.collecting`). `build.review.reviewing`, the one state that\n # WRITES a review file, references neither tag — the agent never authors a\n # footnote.\n footnoteFoldIn: |-\n - A footnote (`[^name]` plus its `[^name]:` definition) is a comment on\n its exact anchor — fold it in as a mandatory concern described\n against that anchor's hunk or paragraph, never flattened into a\n whole-file remark\n - DELETE it in this same turn — marker and definition together — the\n way a transient hand-written code comment is already treated. Never\n re-read a footnote already acted on\n - A footnote is human input only — you reply in prose; never write one\n yourself\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n - Write the closing message for the process HEAD closes or sits inside —\n for a squash, an amend, or a PR body. Starting cold: read every\n decision out of the commits below, not assumed context\n - Cover the motivation, the decisions, the trade-offs, and the\n high-level architectural changes — never which files changed; `git\n diff --stat` is for that, not this message\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>` — exactly what\n a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # `&suiteCheck` below (the output-capture / empty-output-fallback /\n # HEAD-stamp body, from `<%~ it.vars.testCommand %>` through the\n # trailing if/else) is DUPLICATED — not aliased — into\n # `healthGate.check` further down this file: a YAML alias reuses a\n # whole scalar node, and `healthGate.check`'s script needs its own\n # PRIOR_FEEDBACK.md prologue TEXTUALLY BEFORE that shared body inside\n # the SAME Eta-templated string, which plain YAML has no mechanism to\n # express (no string-concatenation-of-aliases, and the shared body\n # itself still needs live `<%~ it.vars.testCommand %>` interpolation,\n # so pulling it into a `vars:` entry would freeze that reference as\n # dead text instead of evaluating it). A change to this body — the\n # capture/fallback/stamp logic — MUST be mirrored into\n # `healthGate.check`'s copy by hand; neither test suite nor YAML\n # tooling catches a drift between them.\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/judge/escalate trio for build.health.*/packages.item.health.*\n # — the caller's own coder fix-state ($onRed), not this machine, handles\n # red. `check` writes `.gtd/PRIOR_FEEDBACK.md` from the last COMMITTED red\n # round (found by walking history, not HEAD — the intervening fix turn\n # always deletes `.gtd/FEEDBACK.md` from HEAD once it believes it resolved\n # the failure) only when one exists, so the FIRST red round of an episode\n # (nothing to compare against yet) bypasses `judge` straight to `$onRed`,\n # paying for no judgment. `retry:` stays on `$onRed` (see\n # `packageItem`/`buildTail` below) rather than moving onto `judge` here:\n # `judge`'s only direct structural source is `check`, but `fix`/`fix-suite`\n # sits between every pair of `judge` visits and is NOT one of `judge`'s\n # sources, so `episodeVisits` (`PatternMachine.ts`) would reset `judge`'s\n # own count on every single pass and a cap declared here could never fire.\n # `$onRed` has TWO direct sources instead (`check`'s own bypass row, and\n # `judge`'s `routes:` catch-all below) — sound accumulation under\n # `sourcesOf`'s single-hop rule, the same shape the bundled retry-workflow\n # tests already pin.\n healthGate:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.escalationPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onGreen, onRed, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # DUPLICATES (not aliases) `entryGate.check`'s `&suiteCheck` body from\n # `<%~ it.vars.testCommand %>` through the trailing if/else — see the\n # comment on that anchor for why plain YAML/Eta can't express \"this\n # state's own PRIOR_FEEDBACK.md prologue, THEN the shared body\" any\n # other way. Mirror a change to that shared portion here by hand.\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n rm -f .gtd/PRIOR_FEEDBACK.md\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check (same as entryGate.check's own sweep).\n rm -f .gtd/REVIEW_RAW.md\n # Bound the PRIOR_FEEDBACK.md search to the CURRENT episode, not the\n # whole process (`it.startCommit`): HEAD's own subject already reads\n # \"... → <%= it.state %>\" (the commit that just entered this check —\n # `PatternMachine.ts`'s `stateSubject`/`TRANSITION_SEP`), so the\n # SECOND most recent such subject is the last time this exact check\n # was entered before now. Reaching THIS check always requires\n # passing through it (green or red), so that prior entry is never\n # itself carrying an unrelated gate's FEEDBACK.md the way\n # `it.startCommit` (spanning the whole process, every earlier gate\n # included) could. No second match at all means this is the very\n # first visit ever — nothing to bound against, so there is no prior\n # round full stop (never falls back to `it.startCommit`, which would\n # reintroduce exactly the cross-episode leak this bounds against).\n episode_anchor=$(git log --format='%H %s' <%= it.startCommit %>..HEAD \\\n | grep -F -- ' → <%= it.state %>' | sed -n '2p' | cut -d' ' -f1)\n if [ -n \"$episode_anchor\" ]; then\n prior_commit=$(git log --format=%H --diff-filter=AM \"$episode_anchor\"..HEAD -- .gtd/FEEDBACK.md 2>/dev/null | head -n 1)\n if [ -n \"$prior_commit\" ]; then\n git show \"$prior_commit\":.gtd/FEEDBACK.md > .gtd/PRIOR_FEEDBACK.md 2>/dev/null\n fi\n fi\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n rm -f .gtd/PRIOR_FEEDBACK.md\n # `.gtd/ESCALATION.md` is swept ONLY here, on a genuinely green\n # result — never on a still-red round, so an unresolved analysis\n # a fix turn left in place (fixFeedbackPrompt never deletes it)\n # survives every retry within the same episode. That also makes\n # its deletion a reliable \"this episode's escalation budget just\n # reset\" signal: `escalate`'s own script (below) anchors its\n # round count on the most recent such deletion.\n rm -f .gtd/ESCALATION.md\n fi\n on:\n \"A .gtd/PRIOR_FEEDBACK.md\": judge\n \"M .gtd/PRIOR_FEEDBACK.md\": judge\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n # The judged retry: a `choice` between identical/new-failure/progress\n # over `.gtd/FEEDBACK.md` (this round) vs `.gtd/PRIOR_FEEDBACK.md` (the\n # previous round), both committed by `check` above so `it.read` can\n # reach them (the evidence rule — no gathering turn). An unaware driver\n # sees only `message:` and lands with a clean tree, taking the ordinary\n # `C` row to `$onRed` — the same conservative target `routes:`'s own\n # catch-all names, so a skipped judgment and a \"keep fixing\" verdict\n # both land the same place; only the `Gtd-Judge:` trailer (or its\n # absence) tells them apart.\n judge:\n actor: judge\n label: Judging the retry\n message: |\n The check is still red, and this isn't the first round —\n `.gtd/FEEDBACK.md` (this round) and `.gtd/PRIOR_FEEDBACK.md` (the\n previous round) are both on disk. Run `gtd judge answer` and pipe a\n verdict — identical, new-failure, or progress — or land (with or\n without an edit of your own) to accept the conservative default\n (retry the fix) with no verdict recorded.\n judge: |-\n {\"state\": <%~ JSON.stringify({ current: it.read(\".gtd/FEEDBACK.md\"), previous: it.read(\".gtd/PRIOR_FEEDBACK.md\") }) %>, \"questions\": [{\"id\": \"verdict\", \"primitive\": \"choice\", \"instructions\": \"Compare this round's failing check output (current) against the previous round's (previous), both in state. Classify the change.\", \"criteria\": \"identical: the same failure restated, byte-for-byte or the same root cause, IGNORING each round's own trailing `<!-- gtd check <sha> -->` stamp (that line always differs and is not part of the failure). new-failure: a materially different symptom than previous. progress: still red, but measurably closer to green (fewer failures, a later stage reached).\"}]}\n routes:\n - question: verdict\n is: identical\n minP: \"<%~ it.vars.judgeIdenticalMinP %>\"\n to: escalate\n - to: $onRed\n on:\n \"C\": $onRed\n # The skipped-judgment fallback (Task 6) must not stall on a dirty\n # tree: every OTHER check/agent state's own script owns the tree, so\n # a wildcard row would be dead; this one is a `judge` gate, where a\n # human may still touch a file while reading the message instead of\n # running `gtd judge answer`. No verdict landed this turn (an\n # ordinary edit, not `gtd judge answer`) is still the skipped-\n # judgment path: the same conservative $onRed target as \"C\", never\n # a refusal.\n \"* **\": $onRed\n # The round-counting gate: no `message:`/`file:` — a plain `check`\n # actor's script decides, from git history, whether this is a fresh\n # escalation (write a new `.gtd/ESCALATION.md`) or the second one (stop\n # for good). The round count is git history, not `retry:`: `retry:`'s\n # `episodeVisits` (`PatternMachine.ts`) resets a target's count the\n # moment a non-source state appears in the trace, and BOTH the `stop`\n # human gate and the fix turn sit between every pair of arrivals here\n # without being sources of either — so no `retry:` on any of the three\n # states below could ever accumulate across escalation rounds.\n escalate:\n actor: check\n label: Counting escalation rounds\n script: |\n #!/usr/bin/env sh\n set +e\n # Anchor on the most recent commit that DELETED .gtd/ESCALATION.md\n # — under `healthGate.check`'s own script (above), that ONLY ever\n # happens on a genuinely green result, never a still-red round\n # (a still-red round leaves an unresolved analysis untouched for\n # the next fix attempt to read). So this is reliably \"the last\n # time this episode's escalation budget was reset\" — unlike\n # `.gtd/FEEDBACK.md`, which a fix turn deletes on EVERY belief it\n # resolved the check, red or green, and so sits between every\n # pair of escalation arrivals regardless of episode. With no such\n # deletion, the whole process is one unbroken streak since the\n # start.\n anchor=$(git log --format=%H <%= it.startCommit %>..HEAD --diff-filter=D -- .gtd/ESCALATION.md 2>/dev/null | head -n 1)\n if [ -z \"$anchor\" ]; then anchor=<%= it.startCommit %>; fi\n # Counting every --diff-filter=AM commit against .gtd/ESCALATION.md\n # would also count the HUMAN's own edit at `stop` — landing that\n # edit produces an M .gtd/ESCALATION.md commit too, and `stop`'s own\n # message explicitly invites that edit. So instead of the file's\n # diff history, grep commit SUBJECTS for `describe` as the FROM\n # state — `stateSubject`'s \"gtd(actor): from → to\" shape means the\n # commit that lands a `describe` turn's own write always reads\n # \"... build.health.describe → build.health.stop\" (or the\n # packages.item.health equivalent), the same narrowing\n # `healthGate.check`'s own `episode_anchor` uses above — never the\n # human's own \"... build.health.stop → build.fix\" landing at `stop`.\n #\n # No `-- .gtd/ESCALATION.md` pathspec on this count: a `prompt`\n # state's clean step is an ATTEMPT by design, not a no-op\n # (`validateHasCRow`'s own doc comment), so `describe`'s landing\n # commit exists every round even when its write is byte-identical\n # to what already sits in the tree — a pathspec would silently drop\n # that commit from the count (git sees no diff on that path) and\n # the 2-round cap would never fire on a repeatedly identical\n # analysis.\n describe_source='<%= it.state.replace(/\\.escalate$/, \".describe\") %>'\n rounds=$(git log --format='%s' \"$anchor\"..HEAD 2>/dev/null | grep -c -F -- \"$describe_source →\")\n if [ \"$rounds\" -ge 2 ]; then\n # Preserve whatever is already in the tree — including a human's\n # own fresh-instructions edit landed at `exhausted` itself (a\n # \"... exhausted → fix\" commit this filter never matches, so it's\n # never mistaken for a describe round either) — rather than\n # overwriting it with the machine's last analysis. Only restore\n # from history when the file is genuinely missing.\n if [ ! -f .gtd/ESCALATION.md ]; then\n last=$(git log --format='%H %s' \"$anchor\"..HEAD -- .gtd/ESCALATION.md 2>/dev/null \\\n | grep -F -- \"$describe_source →\" | head -n 1 | cut -d' ' -f1)\n git show \"$last\":.gtd/ESCALATION.md > .gtd/ESCALATION.md 2>/dev/null\n fi\n # Stamp with HEAD so this step's own change registers as a real\n # M/A edit even when the tree's content is otherwise unchanged\n # (the common case — nothing else touches the file between\n # rounds), the same technique `healthGate.check`'s own\n # FEEDBACK.md stamp uses.\n printf '\\n<!-- gtd escalate %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/ESCALATION.md\n fi\n on:\n \"A .gtd/ESCALATION.md\": exhausted\n \"M .gtd/ESCALATION.md\": exhausted\n \"C\": describe\n describe:\n actor: agent\n label: Describing the escalation\n file: FEEDBACK.md\n skills: <%= it.vars.escalateSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn writes is `.gtd/ESCALATION.md`\n - Read `.gtd/FEEDBACK.md` (this round's failing check output),\n `.gtd/PRIOR_FEEDBACK.md` when present (an earlier round's — this\n may be the first round, with no prior round to compare), and the\n code your own earlier attempts touched\n - Write `.gtd/ESCALATION.md`: what is failing, why the previous\n attempts did not resolve it, and concrete suggested approaches to\n solve it\n - Never fix the code yourself — this turn only writes the document\n on:\n \"A .gtd/ESCALATION.md\": stop\n \"M .gtd/ESCALATION.md\": stop\n \"C\": stop\n \"* **\": stop\n stop:\n actor: human\n label: $escalateLabel\n file: ESCALATION.md\n message: |\n The agent could not get the check to pass after repeated attempts,\n and has written `.gtd/ESCALATION.md`: what is failing, why the\n previous attempts didn't resolve it, and suggested approaches.\n\n Edit it — narrow it, redirect it, add what you know — or land it\n untouched to hand it to the next fix turn as-is.\n on:\n \"C\": $onRed\n \"* **\": $onRed\n exhausted:\n actor: human\n label: Escalation exhausted\n file: ESCALATION.md\n message: |\n Escalation attempts are exhausted — this is the second round, and\n the check is still red. `.gtd/ESCALATION.md` holds the last\n unresolved analysis, and `.gtd/FEEDBACK.md` the last failing\n output.\n\n Edit `.gtd/ESCALATION.md` with fresh instructions for the next fix\n turn, or land untouched to give it one more attempt at the same\n analysis.\n on:\n \"C\": $onRed\n \"* **\": $onRed\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` is instead the `answer` param's own `file:` (the human gate).\n # `check`'s own script picks between REQUIREMENTS.md/ARCHITECTURE.md by\n # testing which file exists, at runtime, inside the shared script.\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"C\":\n to: $onRevise\n action: Accept as-is\n describe: >-\n change nothing and re-run to advance with the questions\n unanswered — the plan stands as written.\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, or delete a question to skip it. To\n accept the plan as-is instead, revert everything and re-run —\n a clean tree is the only accept gesture.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n skills: <%= it.vars.triageSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state file this turn touches is `.gtd/REQUIREMENTS.md`\n — no other files for notes or output\n - `.gtd/TODO.md` is the likely home of the sketch that started\n this process — the human's input, folded into the concerns\n below like any other part of the start diff; never a state\n file to preserve, never gtd bookkeeping to ignore\n - This process started at commit `<%= it.startCommit %>`,\n reverted out of the tree by `unwind` right after landing, so\n `git diff <%= it.startCommit %>` is now empty — its content\n survives only in history. Find the entry commit yourself:\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's first turn, before any baseline-repair\n commits), then `git show` it — a hand-edit, a scratch note,\n or both\n - Whichever lap this is, leave `.gtd/REQUIREMENTS.md`\n uncommitted and finish once it reflects this lap's own work\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's change left there). Build it from that start diff into an\n ordered list of concerns, each classified below.\n\n ### Classify each concern\n\n Classify each as PRODUCT (user-facing/requirements) or TECHNICAL\n (implementation).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — technical ones\n wait for the next phase. When every concern is TECHNICAL, write\n no `## Open Questions` section. STRICT: answer it yourself only\n when the product default is unmistakable; a wrong intent costs a\n whole rebuild lap.\n\n ### Fold in the sketch\n\n - Fold everything the entry commit added under the concern it\n belongs to — a scratch note and a real code edit are both\n just a sketch to finish, never work to preserve; `unwind`\n already reverted both, so there is nothing left to delete\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n - `.gtd/REQUIREMENTS.md` already holds concerns with no\n ticked-but-unfolded answer waiting — a completed REVIEW round\n put this here, not a question you asked. Develop those\n concerns further with whatever the round raised; never\n rediscover or regroup them cold, the way the first lap does\n - The human's review-round edit was reverted the same way the\n entry commit was — read it from history:\n `git show <%= it.reviewBase %>`. (On the first lap that hash\n is the process's own diff base, `<%= it.startCommit %>` — this\n branch doesn't apply)\n - Every decision under `## Answered Questions` stays settled —\n never re-open one. A genuinely open PRODUCT point may still\n raise a fresh `## Open Questions` entry: the product gate sits\n on this path exactly as on the first lap\n - Before grouping anything, run `<%= it.vars.testCommand %>`\n yourself — a loop-back runs no green-baseline gate, so the\n tree may already be red (reverting the edit can undo a fix it\n made). If red, make that breakage the first concern, ahead of\n everything else — every later concern's green-on-its-own\n property assumes the suite started green\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n Answering here closes a gap between what you want the product to\n do and what gets built; changing nothing and re-running says that\n gap is already closed.\n\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered — with one\n escape: change nothing and re-run to advance with the questions\n unanswered.\n\n You can also leave a footnote alongside an answer — it never\n substitutes for ticking a box, which is still required before\n stepping is allowed:\n\n <%~ it.vars.footnoteRules %>\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 # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n skills: <%= it.vars.architectureSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state files this turn touches are\n `.gtd/ARCHITECTURE.md` (write it) and `.gtd/REQUIREMENTS.md`\n (delete once folded in) — no other files for notes or output\n - You do not resume the design conversation — a separate\n machine, its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full; treat every decision there, PRODUCT or\n TECHNICAL alike, as settled — never re-open it\n - Cold means no memory of triage's own back-and-forth, not no\n git access. This process started at commit\n `<%= it.startCommit %>`. Find the first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` it to see what started this process — a\n hand-edit, a scratch note, or both\n - Once `.gtd/ARCHITECTURE.md` is written, delete\n `.gtd/REQUIREMENTS.md` — folded in, it must not linger. Leave\n everything uncommitted and finish\n\n ## First lap\n\n - Develop `.gtd/ARCHITECTURE.md` from those concerns: for each,\n in order, work out the *how*, building on the settled *what*\n - You now know the *how*, so you know each concern's file footprint\n — list each concern's primary paths. Merge concerns whose\n footprints center on the same files into one, unless the later\n one only consumes an interface the earlier one creates (keeps a\n build-on-top sequence from collapsing into one blob). This\n authority is to merge only, never to split — the whole problem\n is over-granularity\n - Record every merge under `## Merged Concerns`,\n carrying both merged requirements verbatim so spec review\n still covers each independently\n - A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human sees it when reviewing the plan, and spec review is the\n real safety net\n - Prefer fewer, larger packages — the smallest independently\n valuable change, not the smallest change that compiles\n - Every open point here is TECHNICAL — triage already resolved\n the product ones, one phase earlier. PERMISSIVE: answer it\n yourself unless you genuinely cannot defend a default; a wrong\n technical call is still caught at spec review\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n Answering here closes a gap between what you want built and how\n it actually gets built; changing nothing and re-running says\n that gap is already closed.\n\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered — with one\n escape: change nothing and re-run to advance with the questions\n unanswered.\n\n You can also leave a footnote alongside an answer — it never\n substitutes for ticking a box, which is still required before\n stepping is allowed:\n\n <%~ it.vars.footnoteRules %>\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 skills: <%= it.vars.decomposeSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n - The only state files this turn touches are the package files\n under `.gtd/packages/` and `.gtd/ARCHITECTURE.md` (deleted) —\n no other files for notes or output\n - Work from `.gtd/ARCHITECTURE.md` if you wrote it earlier this\n conversation, otherwise read it (the converged technical\n plan). It already lists an ordered set of concerns with every\n merge/split judgement made — this turn is a mechanical\n write-out, not a planning one. A `## Merged Concerns` heading\n there records those merges, never a concern of its own: write\n no package file for it\n - Write one package file per concern, in the settled order,\n under `.gtd/packages/` (e.g. `.gtd/packages/01-name.md`,\n `02-name.md`, ...), each carrying that concern's\n requirement(s) — both, independently, if merged — its\n independent tasks, and each task's acceptance criteria as\n `- [ ]` checkboxes and relevant paths\n - Do not merge or split concerns here — that judgement already\n happened; carry the settled grouping over verbatim. No\n package file may reference any other `.gtd/` file\n - Once written, delete `.gtd/ARCHITECTURE.md`. Leave everything\n uncommitted and finish\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: pre\n states:\n # The review pre-judge: three nouls over the reviewBase→worktree diff,\n # judged BEFORE the agent's own authoring lap. Never routes off\n # `routes:` directly — `matchRoute` (`src/PatternMachine.ts`) SKIPS a\n # row whose question has no landed answer, so a bare\n # conjunction-by-inversion table here would let a PARTIAL verdict\n # (fewer than all three questions answered) fall through every escape\n # row straight to the catch-all fast path, the exact hole\n # `specReview.pre`'s own comment documents for its padded-slot design.\n # Unconditionally hands off to `preCheck` instead, which recomputes\n # the whole decision fresh from the landed `Gtd-Judge:` trailers —\n # the same recompute-from-scratch shape `packages.item.spec.scoping`\n # uses for the identical reason.\n pre:\n actor: judge\n label: Judging whether this round needs a full review lap\n message: |\n Judging whether this round of changes is mechanical, touches no\n public surface, and changes no behavior — confident enough on all\n three to skip the agent's own review lap and land straight at the\n human stop with a machine-written summary. Run `gtd judge answer`\n and pipe a verdict for `mechanicalOnly`, `touchesPublicAPI`, and\n `changesBehavior` — or land untouched to run the full lap (the\n conservative default; a skipped judgment never suppresses it).\n judge: |-\n <% const diffText = it.diff(it.reviewBase) %>{\"state\": {\"reviewBase\": \"<%= it.reviewBase %>\", \"diff\": <%~ JSON.stringify(diffText) %>}, \"questions\": [\n {\"id\": \"mechanicalOnly\", \"primitive\": \"noul\", \"instructions\": \"Over state.diff (the change from reviewBase to the working tree), is every hunk mechanical — formatting, a rename, generated output, or a straight refactor with no behavior change?\", \"criteria\": \"Answer no if any hunk could plausibly change what the code does, even slightly.\"},\n {\"id\": \"touchesPublicAPI\", \"primitive\": \"noul\", \"instructions\": \"Over the same state.diff, does any hunk touch a publicly exported symbol, CLI flag, config key, or documented file format?\", \"criteria\": \"Answer yes if uncertain which surface counts as public.\"},\n {\"id\": \"changesBehavior\", \"primitive\": \"noul\", \"instructions\": \"Over the same state.diff, does any hunk change runtime behavior visible to a user or a test?\", \"criteria\": \"Answer yes if uncertain.\"}\n ]}\n on:\n \"C\": preCheck\n \"* **\": preCheck\n # Recomputes the fast-path decision fresh from the just-landed\n # `Gtd-Judge:` trailers — never trusts a verdict to be complete.\n # Requires ALL THREE questions answered as `mechanicalOnly: yes`,\n # `touchesPublicAPI: no`, `changesBehavior: no`, each at\n # `reviewFastPath` confidence or better; a missing question, a\n # malformed answer, a wrong answer, or a low-confidence right answer\n # ALL fail the SAME way — nothing written, a clean tree, `\"C\"` routes\n # to `reviewing`. Only a fully-confident, fully-answered \"yes to\n # mechanical, no to the other two\" verdict writes the marker that\n # routes to `fastReview`.\n preCheck:\n actor: check\n label: Scoping the fast path\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/REVIEW_FAST.md\n threshold=<%~ it.vars.reviewFastPath %>; trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n fast=1\n for spec in mechanicalOnly:yes touchesPublicAPI:no changesBehavior:no; do\n id=${spec%%:*}\n want=${spec#*:}\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"$id\\\"\" | head -n 1)\n if [ -z \"$line\" ]; then\n fast=0\n continue\n fi\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n if [ \"$answer\" != \"$want\" ]; then\n fast=0\n continue\n fi\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n if [ -z \"$threshold\" ] \\\n || ! awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p>=t)}' 2>/dev/null; then\n fast=0\n fi\n done\n if [ \"$fast\" -eq 1 ]; then\n : > .gtd/REVIEW_FAST.md\n fi\n on:\n \"A .gtd/REVIEW_FAST.md\": fastReview\n \"C\": reviewing\n # The fast path: a `check`-actor script writes `.gtd/REVIEW.md` itself\n # — the pre-judge already established the round is mechanical,\n # touches no public surface, and changes no behavior, so there is\n # nothing for the agent's own `reviewing` lap to add. The human stop\n # is never skipped: this still routes to `await-review`. An empty\n # diff (a fast-pathed round with nothing to point at — reachable from\n # a clean-tree `--entry review-gate.check`) still needs at least one\n # pointer: `.gtd/REVIEW.md` itself is the one path this script can\n # always point at, since `gtd check review`'s own parser refuses a\n # chunk with none.\n fastReview:\n actor: check\n label: Writing the review summary\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/REVIEW_FAST.md\n {\n printf '# Review: %s\\n\\n' \"$(git rev-parse --short HEAD)\"\n printf '<!-- base: <%= it.reviewBase %> -->\\n\\n'\n printf '## Changes\\n\\n'\n files=$(git diff --name-only <%= it.reviewBase %>)\n if [ -n \"$files\" ]; then\n printf '%s\\n' \"$files\" | while IFS= read -r f; do\n printf -- '- [ ] ./%s — changed\\n' \"$f\"\n done\n else\n printf -- '- [ ] ./.gtd/REVIEW.md — no file changes this round\\n'\n fi\n } > .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW.md\": await-review\n \"M .gtd/REVIEW.md\": await-review\n \"* **\": await-review\n \"C\": await-review\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n skills: <%= it.vars.reviewSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is `.gtd/REVIEW.md` —\n no other files for notes or output\n\n Write `.gtd/REVIEW.md` in this exact format, to help a human\n review the changes:\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\n semantically (same feature/refactor/fix, across files), each\n with a short explanation of what changed and why, then one\n pointer per hunk (`./`-relative path, optional `#line`;\n checkboxes are for the human, not you). Put the note's\n opening line right on the pointer's line:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n Continue a longer note below the pointer, indented exactly two spaces\n — never four or more, which reads as a code block and never\n reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note sitting entirely on the line(s) beneath the pointer is\n also valid. Either way, the note must never start with a bare `./path` token\n — that parses as a second pointer, not a note\n No diff is given — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns\n plus anything pending); on a feedback round that's the previous\n review's boundary, so it covers only what's new.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off. Ticks are read-progress only: landing clears\n every box back to `- [ ]` on disk and nothing records which hunks\n you read — there is no persisted trail of it.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, a footnote anchored to a hunk, or a\n direct code edit — to send a FULL development lap\n (**review.deciding** → **review.triage** → **review.triaging**\n → **review.collecting** → re-triage; a hand-edit outside\n `.gtd/` skips straight from **review.deciding** to\n **review.collecting**, no verdict of your own required). A\n 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 A footnote works the same way here as a line note:\n\n <%~ it.vars.footnoteRules %>\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note or hand-edited any\n # file this round outside .gtd/; otherwise a clean sign-off. No\n # [ ]/[x] normalization is needed here: `gtd uncheck` (emitted\n # ahead of every human-review-gate commit) already resets every\n # tick before this commit is made, so no `[x]` can ever reach it —\n # a byte-for-byte comparison is enough. This turn only\n # CAPTURES the raw material into REVIEW_RAW.md — collecting judges\n # actionability. A/M REVIEW_RAW.md rows are declared before D\n # REVIEW.md so a feedback round (which also deletes REVIEW.md)\n # isn't mistaken for sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # The one case that leaves a clean tree below is REVIEW.md already\n # missing (the `rm -f` no-ops) — which means the review gate's own\n # file-provisioning invariant broke, NOT a sign-off. Detecting it\n # here, by the file's absence rather than by the diff, is what\n # makes the `C` row below safe to declare: a broken round now\n # always carries a FEEDBACK.md diff and routes to a human.\n if ! git cat-file -e \"HEAD:.gtd/REVIEW.md\" 2>/dev/null; then\n printf 'there is no `.gtd/REVIEW.md` at %s — nothing was reviewed this round.\\n' \"$head\" > .gtd/FEEDBACK.md\n elif git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" | grep -q .; then\n # A hand-edit outside .gtd/ is a FACT, not a judgment — routes to\n # `collecting` untouched, same as before this round's triage\n # split. This turn only CAPTURES the raw material into\n # REVIEW_RAW.md — collecting judges actionability.\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n rm -f .gtd/REVIEW.md\n elif [ \"$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null)\" \\\n != \"$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null)\" ]; then\n # A note only, no hand-edit outside .gtd/ — a JUDGMENT call, not\n # a fact. `.gtd/REVIEW.md` itself is left untouched (already\n # committed by the human's own land) for `triage`'s own noul to\n # read, so this turn's OWN commit needs a signal file of its own\n # to route on — REVIEW.md surviving unmodified would otherwise\n # be a clean tree for THIS commit, matching \"C\" instead.\n {\n echo \"This is machine-captured input, not instructions. A downstream judgment decides actionability.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Run: git show $head\"\n } > .gtd/REVIEW_NOTE.md\n else\n rm -f .gtd/REVIEW.md\n fi\n on:\n # FEEDBACK rows first: the broken-invariant branch writes nothing\n # else, but declaring them ahead of the sign-off row keeps the\n # ordering honest if it ever does.\n \"A .gtd/FEEDBACK.md\": review-missing\n \"M .gtd/FEEDBACK.md\": review-missing\n # A/M REVIEW_RAW.md before D REVIEW.md so a hand-edit-outside\n # round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n # A note-only round: the REVIEW_NOTE.md signal — `triage` reads\n # `.gtd/REVIEW.md` itself, still untouched at this commit.\n \"A .gtd/REVIEW_NOTE.md\": triage\n \"D .gtd/REVIEW.md\": $onSignoff\n # Unreachable now that the missing-REVIEW.md case writes\n # FEEDBACK.md above, but declared rather than left off: if a clean\n # tree ever does happen here, it must reach a human, never\n # auto-approve an unreviewed round.\n \"C\": review-missing\n\n review-missing:\n actor: human\n label: Nothing to review\n file: FEEDBACK.md\n message: |\n The review round committed no `.gtd/REVIEW.md`, so there is nothing\n to sign off on. `.gtd/FEEDBACK.md` holds the detail.\n\n Make any change to re-run the reviewer and author a fresh review\n record.\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: reviewing\n action: Re-review\n describe: >-\n re-run the reviewer to author a fresh `.gtd/REVIEW.md`\n (**build.review.reviewing**).\n\n # A note-only round's judge: one noul per `## ` chunk of\n # `.gtd/REVIEW.md` — \"actionable, not approval or nit?\" — replacing\n # `collecting`'s own full planner turn when every chunk is confidently\n # non-actionable. `it.sections` is the same real markdown parse\n # `src/steering/review.ts`'s own chunk splitter builds on (both walk\n # `MarkdownTree.ts`'s `headingText` over the document's depth-2\n # headings), so chunk N here numbers identically to that format's own\n # chunks — the same guarantee `specReview.pre`'s comment documents for\n # its own `it.sections` use.\n triage:\n actor: judge\n label: Judging feedback actionability\n message: |\n Judging whether each `## ` chunk's note in `.gtd/REVIEW.md` is\n actionable, to skip `collecting`'s full turn when the round is\n approval-only. Run `gtd judge answer` and pipe a verdict per\n chunk — or land untouched to run the full triage (the\n conservative default; a skipped judgment never signs off).\n judge: |-\n <%\n const chunks = it.sections(\".gtd/REVIEW.md\")\n const questions = chunks.map((title, i) => ({\n id: `chunk-${i + 1}`,\n primitive: \"noul\",\n instructions: `Is the note under review chunk \"${title}\" (in .gtd/REVIEW.md) actionable — anything beyond an approving remark with no code edit?`,\n criteria: \"A concrete request, a question, a code comment, or a hand-edit under this chunk answers yes. No note, or a purely approving remark, answers no.\",\n }))\n %>{\"state\": <%~ JSON.stringify({ review: it.read(\".gtd/REVIEW.md\") }) %>, \"questions\": <%~ JSON.stringify(questions) %>}\n on:\n \"C\": triaging\n \"* **\": triaging\n # Recomputes actionability fresh from the just-landed `Gtd-Judge:`\n # trailers and `.gtd/REVIEW.md`'s own chunk count — never trusts\n # `triage`'s verdict to be complete. An unanswered chunk (a skipped\n # judgment, a partial verdict, or a dirty land) defaults to\n # ACTIONABLE — the conservative direction, matching `triage`'s own\n # message (\"land untouched to run the full triage\"). Its `awk`\n # heading scan must number chunks the same way `it.sections` did for\n # `triage`'s own `chunk-N` ids — same constraint `scoping`'s comment\n # documents for `specReview`.\n triaging:\n actor: check\n label: Filtering non-actionable feedback\n script: |\n #!/usr/bin/env sh\n set +e\n threshold=<%~ it.vars.reviewNoteActionable %>; head=$(git rev-parse HEAD)\n trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n # `it.sections`'s real mdast parse (CommonMark) numbered `chunk-N`\n # against every TOP-LEVEL depth-2 heading — never one absorbed as\n # a list item's own lazy continuation. A chunk's own pointer lines\n # are always `- ` list items (2-space content column), so a `##`\n # indented 2-3 spaces right after one stays absorbed into that\n # list under BOTH parsers — a bare `/^## /` scan is correct there,\n # and widening it would instead miscount a note's own continuation\n # line that happens to start with `##` as informal markdown. Only\n # a SINGLE leading space unconditionally breaks a `- ` list's\n # continuation and becomes a real top-level heading either way,\n # regardless of what precedes it — the one indent depth `/^## /`\n # alone would miss.\n total=$(awk '\n /^```/ { f = !f; next }\n f { next }\n /^ ?## / { c++ }\n END { print c + 0 }\n ' .gtd/REVIEW.md 2>/dev/null)\n [ -n \"$total\" ] || total=0\n actionable=0\n i=1\n while [ \"$i\" -le \"$total\" ]; do\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"chunk-$i\\\"\" | head -n 1)\n # Missing entirely (a skipped judgment, or a partial verdict\n # that never answered this chunk) defaults to actionable — the\n # one default direction this gate must never get wrong.\n this_one=1\n if [ -n \"$line\" ]; then\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n if [ \"$answer\" = \"no\" ]; then\n this_one=0\n elif [ \"$answer\" = \"yes\" ]; then\n this_one=1\n if [ -n \"$threshold\" ] \\\n && awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p<t)}' 2>/dev/null; then\n this_one=0\n fi\n fi\n fi\n [ \"$this_one\" -eq 1 ] && actionable=1\n i=$((i + 1))\n done\n [ \"$total\" -eq 0 ] && actionable=1\n if [ \"$actionable\" -eq 1 ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n rm -f .gtd/REVIEW.md .gtd/REVIEW_NOTE.md\n else\n rm -f .gtd/REVIEW.md .gtd/REVIEW_NOTE.md\n fi\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n \"C\": collecting\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are judging and classifying a round of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.footnoteFoldIn %>\n\n - The only state files this turn touches are\n `.gtd/REQUIREMENTS.md` and `.gtd/REVIEW_RAW.md` (deleted) —\n you classify, you do not build\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. Work from what you already reviewed if you\n wrote today's review earlier this conversation; otherwise read\n that commit's diff yourself first.\n\n The round is actionable if any of these hold:\n\n - The human left a note on `.gtd/REVIEW.md`. A note is a mandatory\n concern below\n - The human added a code comment this round, even a plain-prose\n one — describe it as a concern, and note the comment line\n itself is transient: it must not survive the lap that\n satisfies it\n - The human hand-edited non-comment code this round — no longer\n a committed intent to build on, but a sketch like the entry\n commit's own diff. Describe what it was reaching for; expect\n the next lap to re-derive it from scratch, never call it final\n\n Not actionable only when none of the above holds — nothing but an\n approving remark, no code edit, no substantive note. Never invent\n actionability, and never dismiss a real note or edit as approval.\n\n - If actionable: write `.gtd/REQUIREMENTS.md` with an ordered\n list of concerns, each PRODUCT or TECHNICAL — the shape\n `design.triage` builds, one `## <heading>` per concern in\n build order. Fold every note, comment, and hand-edit in under\n its concern. Raise no open questions here — `design.triage`\n owns that later. Then delete `.gtd/REVIEW_RAW.md` and finish\n - If not: delete `.gtd/REVIEW_RAW.md` and finish, writing\n nothing to `.gtd/REQUIREMENTS.md` — that alone is the sign-off\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: pre\n states:\n # One noul per `## ` section of the package file, padded to a FIXED\n # 8-slot id set (section-1..section-8): a missing real section at a\n # slot still renders — never omits — that slot's question, so the\n # compiler's own load-time stub check (`judgeQuestionIds`) sees the\n # same 8 ids on both its stub renders regardless of real content. A\n # package with more than 8 real sections fails open on every slot (the\n # `overflow` branch below) — full review, never a silent partial one.\n #\n # Deliberately carries NO `routes:` — a verdict answering only SOME of\n # the 8 slots (the expected shape: real slots plus whatever padding a\n # driver bothers to answer) must never let an unanswered REAL section\n # slip through as an implicit approval, and `routes:`'s own\n # first-match-wins matching (`matchRoute`, `src/PatternMachine.ts`) has\n # no way to require \"every question was answered\" — an absent answer\n # just falls through every row untouched, unable to tell \"this is\n # padding\" from \"the driver skipped a real requirement\". `scoping`\n # below owns the whole approve/scope decision instead, recomputing\n # fresh from the just-landed `Gtd-Judge:` trailers AND the package's\n # own real section count every time — an unanswered real section\n # defaults to failing there, closing the hole structurally rather than\n # trusting every future verdict to be complete.\n pre:\n actor: judge\n label: Judging spec coverage\n message: |\n Judging whether the code already satisfies each requirement in the\n package spec, before spending a full review turn. Run `gtd judge\n answer` and pipe a verdict per section — or land untouched to run\n the full review (the conservative default; a skipped judgment\n never suppresses anything).\n judge: |-\n <%\n const pkgPath = it.read(\".gtd/NEXT.md\").trim()\n const pkg = it.read(pkgPath)\n const sections = it.sections(pkgPath)\n const MAX_SECTIONS = 8\n // More real sections than slots: fail open on EVERY slot rather\n // than silently judging only the first 8 and letting the rest\n // through unreviewed — the one direction this gate must never\n // fail in (see the review feedback this fixed).\n const overflow = sections.length > MAX_SECTIONS\n const questions = []\n for (let i = 0; i < MAX_SECTIONS; i += 1) {\n const title = sections[i]\n if (overflow) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"This package has more `## ` sections than this gate can judge (max 8) — always answer no, never yes: a confident approval here would silently skip review of the sections beyond the eighth.\",\n criteria: \"Structural, not a judgment call — no is the only correct answer.\",\n })\n } else if (title !== undefined) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: `Is the requirement \"${title}\" already fully satisfied by the code on the range from ${it.startCommit} to the working tree?`,\n criteria: \"Judge from the package markdown plus that range, read yourself. Only answer yes at a probability clearing the threshold below if genuinely confident nothing in this section is missing.\",\n })\n } else if (sections.length === 0) {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"This package has no `## ` sections at all — there is nothing to judge. Always answer no, never yes: a confident approval here would silently skip the only review this package would ever get.\",\n criteria: \"Structural, not a judgment call — no is the only correct answer.\",\n })\n } else {\n questions.push({\n id: `section-${i + 1}`,\n primitive: \"noul\",\n instructions: \"Padding slot: this package has fewer than 8 `## ` sections. There is no corresponding requirement.\",\n criteria: \"Always answer yes at p 1 — nothing to evaluate.\",\n })\n }\n }\n %>{\"state\": <%~ JSON.stringify({ package: pkg }) %>, \"questions\": <%~ JSON.stringify(questions) %>, \"specPreJudgeThreshold\": \"<%= it.vars.specPreJudge %>\"}\n on:\n \"C\": scoping\n # A dirty-tree land with no verdict piped — package 01's own\n # `build.health.judge`/`packages.item.health.judge` fix for this\n # same shape: the skipped-judgment fallback must not stall on a\n # dirty tree either, so it takes the same conservative target \"C\"\n # does — `scoping` recomputes the real decision fresh either way.\n \"* **\": scoping\n # Owns the WHOLE approve/scope decision `pre`'s own comment describes:\n # recomputes fresh from the package's real `## ` sections (never trusts\n # `pre`'s verdict to be complete) and the just-landed `Gtd-Judge:`\n # trailers. An unanswered real section — a partial verdict, a skipped\n # judgment, or a dirty-tree land with no verdict at all — defaults to\n # FAILING: only `.gtd/SPEC_CLEARED.md` (written exclusively when every\n # real section is both answered and confident) approves; every other\n # outcome, including a script bug that produces no output at all,\n # lands on `review` — the fail-open direction can never accidentally\n # become fail-approve.\n #\n # The `awk '/^```/{f=!f} !f && /^## /{...}'` heading scan below MUST\n # number sections the same way `it.sections` (an mdast parse,\n # `src/steering/MarkdownTree.ts`'s `headingSections`) numbered them for\n # `pre`'s own `section-N` ids, or a verdict answered against mdast's\n # numbering strikes/scopes the WRONG section here — a real, silent\n # corruption a round of review caught (a `##` line quoted inside a\n # fenced code block is not a heading; shell can't run mdast, so this\n # skips fenced regions by hand instead, the one desync case realistic\n # here). A setext (`---`-underlined) H2 is still a mismatch — no\n # package/finding content in this codebase's own history has ever used\n # one; `docs/`'s own style never does either.\n scoping:\n actor: check\n label: Scoping the review to the failing sections\n script: |\n #!/usr/bin/env sh\n set +e\n rm -f .gtd/SPEC_SCOPE.md .gtd/SPEC_CLEARED.md\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n threshold=<%~ it.vars.specPreJudge %>; if [ -n \"$pkg\" ] && [ -f \"$pkg\" ]; then\n titles=$(awk '/^```/{f=!f} !f && /^## /{sub(/^## /,\"\"); print}' \"$pkg\")\n total=0\n [ -n \"$titles\" ] && total=$(printf '%s\\n' \"$titles\" | wc -l | tr -d ' ')\n if [ \"$total\" -gt 0 ]; then\n trailers=$(git log -1 --format=%B HEAD | grep -o 'Gtd-Judge: {[^}]*}')\n i=1\n while [ \"$i\" -le \"$total\" ]; do\n line=$(printf '%s\\n' \"$trailers\" | grep \"\\\"id\\\":\\\"section-$i\\\"\" | head -n 1)\n # Missing entirely (padding, a skipped judgment, or a real\n # section the verdict just never answered) defaults to\n # failing — the one default direction this gate must never\n # get wrong.\n failing=1\n if [ -n \"$line\" ]; then\n answer=$(printf '%s' \"$line\" | sed -n 's/.*\"answer\":\"\\{0,1\\}\\([a-z]*\\)\"\\{0,1\\}.*/\\1/p')\n # A noul answer is conventionally a JSON boolean\n # (`true`/`false`), never the bare \"yes\"/\"no\" `routes:`\n # matching normalizes it to internally (`asRouteAnswers`,\n # src/step/planStep.ts), but the decode accepts a quoted\n # string too — the committed trailer carries the RAW\n # verdict, so both spellings must clear a section here, or\n # a driver using the string form silently loses the whole\n # optimisation, scoping every section into review forever\n # without ever being wrong.\n case \"$answer\" in (true) answer=yes ;; (false) answer=no ;; esac\n p=$(printf '%s' \"$line\" | sed -n 's/.*\"p\":\\([0-9.eE+-]*\\).*/\\1/p')\n # `[ -n \"$threshold\" ]` guards a BLANK `specPreJudge`: awk\n # treats an empty `-v t=` as the uninitialized strnum `0`,\n # so `p >= t` would be true at ANY probability — turning\n # the workflow's documented \"blank disables\" convention\n # into fail-APPROVE for this one gate. Blank must instead\n # never clear anything, the same failing default as a\n # missing answer.\n if [ \"$answer\" = \"yes\" ] && [ -n \"$threshold\" ] \\\n && awk -v p=\"$p\" -v t=\"$threshold\" 'BEGIN{exit !(p>=t)}' 2>/dev/null; then\n failing=0\n fi\n fi\n if [ \"$failing\" -eq 1 ]; then\n title=$(printf '%s\\n' \"$titles\" | sed -n \"${i}p\")\n [ -n \"$title\" ] && printf -- '- %s\\n' \"$title\" >> .gtd/SPEC_SCOPE.md\n fi\n i=$((i + 1))\n done\n [ -f .gtd/SPEC_SCOPE.md ] || : > .gtd/SPEC_CLEARED.md\n fi\n fi\n on:\n \"A .gtd/SPEC_CLEARED.md\": $onApproved\n \"A .gtd/SPEC_SCOPE.md\": review\n \"* **\": review\n \"C\": review\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n skills: <%= it.vars.specReviewSkills %>\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are reviewing a freshly-built work package against its own\n spec.\n\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md` — write it only when you find problems\n - The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n <% let scope; try { scope = it.read(\".gtd/SPEC_SCOPE.md\") } catch (e) { scope = undefined } %><% if (scope) { %>\n - A pre-judge already found the other sections satisfied. Confine\n your review to only these sections: <%~ scope %>\n <% } %>\n - Verify the implementation against it: tasks done, criteria\n met, code sound and consistent with the codebase. No diff is\n given — read the range yourself, from `<%= it.startCommit %>`\n to the working tree, process-wide (it can span earlier\n packages)\n - You own that bar; nothing downstream re-weighs your findings\n - Write nothing when the package fully satisfies its spec —\n silence is your approval. Otherwise write\n `.gtd/SPEC_FEEDBACK.md` listing what would violate the spec if\n it shipped unaddressed, specific enough to act on, each as its\n own `## ` heading\n - Never fix anything yourself and never delete the package\n file — a later step owns that\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now), any\n # REVIEW_RAW.md the review loop-back left behind, and the quality\n # lap's own state (its queue, its picked lens, its findings and\n # markers) — the only sweeper on that path before any of these\n # would leak into a later `gtd summary` prompt's diff range. A\n # feedback loop-back therefore clears `.gtd/QUALITY_DONE.md` too,\n # re-running the whole lap.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md .gtd/NEXT_REVIEW.md .gtd/QUALITY*.md\n rm -rf .gtd/reviews\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n skills: <%= it.vars.buildSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn may write is `.gtd/SATISFIED.md`;\n never delete the package file (the spec-review gate reads it\n after you) or touch `.gtd/NEXT.md` — `picking` owns it\n - The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n - First check its acceptance criteria against the current tree —\n an earlier fix turn may already satisfy them. If **every**\n criterion is met, implement nothing: write `.gtd/SATISFIED.md`\n with each criterion's concrete evidence (commit, file, or\n symbol), change nothing else, and finish. Otherwise implement\n normally and skip that file\n - Implement every task the package describes, no more, no less,\n fanning independent ones out to parallel subagents where your\n harness supports it; leave other package files untouched\n - Leave the package file in place, everything uncommitted, then\n finish your turn\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n skills: <%= it.vars.fixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n - If the only way to green the suite is another package's work,\n make the smallest change that gets there — that package can\n then legitimately report itself already satisfied later\n - Leave everything uncommitted and finish your turn — do not commit\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n skills: <%= it.vars.reviewFixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md` — address it, then delete it\n - Read it (the reviewer's concerns) and the package spec\n (`<%= it.read(\".gtd/NEXT.md\") %>`), then fix the code to\n resolve every concern\n - Delete `.gtd/SPEC_FEEDBACK.md` once resolved; leave everything\n else uncommitted and finish your turn\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/SPEC_SCOPE.md .gtd/SPEC_CLEARED.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n # Nothing left to sweep (an already-clean NEXT.md/package) still\n # needs to proceed to the next package, not stall here.\n \"C\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The qualitative review lap — one dimension per turn, each its own fresh\n # context (a gtd state invocation IS a separate context window). `seeding`\n # and `picking` are `check` (mechanics only); `reviewing` is `prompt` (the\n # one turn that judges). ▸ planner identity — reviewing is a planner turn,\n # not a coder one; the fix turn lives in buildTail instead (see its\n # `fix-quality`), so this machine stamps no coder model/system anywhere.\n qualityReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onClean, onFindings]\n entry: seeding\n states:\n # Short-circuits on `.gtd/QUALITY_DONE.md` — without it, `build.health`'s\n # green route re-enters this lap forever, since both `build.fix` and\n # `fix-quality` hand back to `build.health.check`. The guard is per\n # EPISODE: written once by `picking` when the queue drains, swept only\n # on the feedback loop-back path (`packageLoop.picking`'s sweep).\n # `seeding`'s script below interpolates `<%~ it.vars.qualityReviews %>`\n # raw into a `list=\"...\"` shell assignment, run with `sh -c` — a value\n # containing `$(...)` or a backtick executes. Accepted: a repository's\n # own gtd config (`.gtdrc`, `GTD_QUALITYREVIEWS`) is TRUSTED input —\n # `testCommand` already reaches `sh -c` raw by design (it IS a command\n # line), so a config that could set `qualityReviews` could already set\n # `testCommand` to anything. This reaches a shell deliberately.\n seeding:\n actor: check\n label: Seeding the quality review queue\n script: |\n #!/usr/bin/env sh\n set +e\n [ -f .gtd/QUALITY_DONE.md ] && exit 0\n mkdir -p .gtd/reviews\n i=1\n list=\"<%~ it.vars.qualityReviews %>\"\n IFS=,\n for name in $list; do\n trimmed=$(printf '%s' \"$name\" | sed 's/^ *//; s/ *$//')\n if [ -n \"$trimmed\" ]; then\n printf '%s' \"$trimmed\" > \"$(printf '.gtd/reviews/%02d-%s.md' \"$i\" \"$trimmed\")\"\n i=$((i + 1))\n fi\n done\n on:\n \"* .gtd/reviews/**\": picking\n \"C\": $onClean\n # The `ls .gtd/reviews/*.md | head -n 1` idiom `packageLoop.picking`\n # already uses — draining its own queue is what ends the loop; the\n # reviewer never touches `.gtd/reviews/` itself.\n picking:\n actor: check\n label: Picking the next quality lens\n script: |\n #!/usr/bin/env sh\n set +e\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/reviews/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n cp \"$next\" .gtd/NEXT_REVIEW.md\n rm -f \"$next\"\n else\n rm -f .gtd/NEXT_REVIEW.md\n : > .gtd/QUALITY_DONE.md\n if [ -s .gtd/QUALITY.md ]; then\n : > .gtd/QUALITY_READY.md\n fi\n fi\n # First-match-wins ordering is what routes a drained-with-findings\n # turn (whose diff carries the queue deletions AND both markers) to\n # the fix turn rather than the clean exit.\n on:\n \"A .gtd/NEXT_REVIEW.md\": reviewing\n \"M .gtd/NEXT_REVIEW.md\": reviewing\n \"A .gtd/QUALITY_READY.md\": $onFindings\n \"* **\": $onClean\n \"C\": $onClean\n reviewing:\n actor: agent\n label: Reviewing (one quality lens)\n file: NEXT_REVIEW.md\n skills: <%~ it.read(\".gtd/NEXT_REVIEW.md\").trim() %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - The only state file this turn writes is `.gtd/QUALITY.md` — no\n other files for notes or output\n - Review the whole assembled change, from `<%= it.startCommit %>`\n to the working tree, through this ONE quality lens only — the\n lens is named in `.gtd/NEXT_REVIEW.md`, already loaded as this\n turn's own skill\n - Where you find something blocking, APPEND a `## ` chunk to\n `.gtd/QUALITY.md` describing it — never overwrite what an\n earlier dimension already wrote there\n - Write nothing when nothing is blocking under this lens — a\n clean turn IS this dimension's approval\n - Touch no other state file, and leave everything uncommitted\n on:\n \"* **\": picking\n \"C\": picking\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n skills: <%= it.vars.fixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n - Leave everything uncommitted — do not commit\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: quality\n onRed: fix\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Sits ahead of the whole humanReview machine — before `pre`, not\n # inside it — so both the fast path and the full path pay it. The lap\n # runs on EVERY round reaching this point, mechanical or not: a wrong\n # `mechanicalOnly` verdict must never be what silently skips it.\n quality:\n machine: qualityReview\n with:\n onClean: review\n onFindings: fix-quality\n\n # A sibling of `fix`, not a state inside `qualityReview`: a machine\n # stamps one `model:`/`system:` on every state it owns, and reviewing\n # is a planner turn while fixing is a coder turn. The split also lets\n # `health.check`/`health.escalate` resolve as plain local targets —\n # targets never resolve upward in this workflow.\n fix-quality:\n actor: agent\n label: Fixing quality findings\n file: QUALITY.md\n skills: <%= it.vars.reviewFixSkills %>\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n - Read `.gtd/QUALITY.md` — one `## ` chunk per quality dimension\n that found something blocking. Merge duplicate findings across\n dimensions FIRST, then fix every chunk\n - Delete `.gtd/QUALITY.md` and `.gtd/QUALITY_READY.md` once every\n finding is resolved\n - Leave everything else uncommitted and finish your turn\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"D .gtd/QUALITY.md\": health.check\n \"* **\": fix-quality\n \"C\": fix-quality\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Hoisted here, 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.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\" 2> .gtd/.unwind-error\n code=$?\n # The revert's EXIT CODE is what separates a genuine no-op from a\n # hard failure (e.g. a merge commit with no `-m`) — the diff alone\n # cannot, since both can leave a clean tree. Turning the failure\n # into a FEEDBACK.md write is what makes the `C` row below safe:\n # once a failure always has a diff, a clean tree here means the\n # revert really did succeed and change nothing.\n if [ \"$code\" -ne 0 ]; then\n printf 'gtd could not unwind %s out of your working tree.\\n\\n' \"$commit\" > .gtd/FEEDBACK.md\n if [ -s .gtd/.unwind-error ]; then\n cat .gtd/.unwind-error >> .gtd/FEEDBACK.md\n else\n printf '`git revert --no-commit` exited %s and produced no output.\\n' \"$code\" >> .gtd/FEEDBACK.md\n fi\n fi\n rm -f .gtd/.unwind-error\n on:\n \"A .gtd/FEEDBACK.md\": unwind-failed\n \"M .gtd/FEEDBACK.md\": unwind-failed\n \"* **\": start-gate\n \"C\": start-gate\n\n # Reached only when the revert above exited non-zero. A human repairs\n # the tree by hand; start-gate.check clears FEEDBACK.md on its way\n # green, so nothing has to sweep it here.\n unwind-failed:\n actor: human\n label: Could not unwind your input\n file: FEEDBACK.md\n message: |\n gtd could not revert your sketch out of the working tree.\n `.gtd/FEEDBACK.md` holds the error.\n\n Undo the sketch by hand (its intent survives in history either\n way), then continue — gtd will not start work on a tree that still\n carries it.\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: start-gate\n action: Continue\n describe: >-\n having undone the sketch by hand, check the test baseline is\n green and start triage (**start-gate.check**).\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, 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.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture-pre\n\n # Judges whether `.gtd/REQUIREMENTS.md` — the committed artifact\n # `design.triage` just settled — warrants a dedicated architecture\n # pass, never an ex-ante complexity score off the bare `.gtd/TODO.md`\n # (that has no committed artifact to judge, and would break the\n # evidence rule every other judge state in this file follows). A\n # single fixed noul, so `routes:` is the right tool here.\n architecture-pre:\n actor: judge\n label: Judging whether this plan warrants an architecture pass\n message: |\n Judging whether `.gtd/REQUIREMENTS.md`'s settled concerns need a\n dedicated architecture pass — real structural decisions, multiple\n integration points, or a non-obvious tradeoff — before packages\n are written. Run `gtd judge answer` and pipe a verdict for\n `architectureWarranted` — or land untouched to run the full pass\n (the conservative default; a skipped judgment never suppresses\n it).\n judge: |-\n {\"state\": {\"requirements\": <%~ JSON.stringify(it.read(\".gtd/REQUIREMENTS.md\")) %>}, \"questions\": [\n {\"id\": \"architectureWarranted\", \"primitive\": \"noul\", \"instructions\": \"Given the settled concerns in `.gtd/REQUIREMENTS.md` (in state), does this plan warrant a dedicated architecture pass — real structural decisions, multiple integration points, or a non-obvious tradeoff — before packages are written?\", \"criteria\": \"Answer yes if uncertain; a trivial, single-concern, mechanical plan with no real design decision answers no.\"}\n ]}\n routes:\n - question: architectureWarranted\n is: \"no\"\n minP: <%~ it.vars.architectureSkipMinP %>\n to: architecture-promote\n - to: architecture\n on:\n \"C\": architecture\n \"* **\": architecture\n\n # The skip path: promotes `.gtd/REQUIREMENTS.md` wholesale into a\n # SINGLE `.gtd/packages/01-<slug>.md` before the queue is entered —\n # never split, `architecture.decompose`'s own job, skipped here.\n # Without this, skipping `architecture.decompose` would leave\n # `.gtd/packages/` empty, so `packageLoop.picking` would match its\n # `\"C\"` row, route `$onDrained`, and the process would close having\n # built nothing.\n architecture-promote:\n actor: check\n label: Promoting the plan straight to a package\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd/packages\n title=$(awk '/^```/{f=!f} !f && /^## /{sub(/^## /,\"\"); print; exit}' .gtd/REQUIREMENTS.md)\n [ -z \"$title\" ] && title=package\n slug=$(printf '%s' \"$title\" | tr '[:upper:]' '[:lower:]' \\\n | sed 's/[^a-z0-9]\\{1,\\}/-/g; s/^-*//; s/-*$//')\n [ -z \"$slug\" ] && slug=package\n mv .gtd/REQUIREMENTS.md \".gtd/packages/01-${slug}.md\"\n on:\n \"* **\": packages\n # Unreachable in practice (`mv` always changes the tree) but\n # declared rather than left off: if a clean tree ever does happen\n # here, it must run the full pass, never silently produce nothing.\n \"C\": architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
54310
54310
  //#endregion
54311
54311
  //#region src/workflows/templates.ts
54312
54312
  const SCHEMA_URL = "https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "12.3.0",
3
+ "version": "12.4.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": {