@pmelab/gtd 6.13.0 → 6.14.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 +9 -6
- package/dist/gtd.bundle.mjs +217 -20
- package/docs/cli.md +7 -7
- package/docs/configuration.md +14 -14
- package/docs/upgrading.md +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,12 +111,15 @@ and tells you to repair it first (that's what `gtd fix` is for). The two
|
|
|
111
111
|
steering-file entries are chosen by which file you create:
|
|
112
112
|
|
|
113
113
|
- Create **`.gtd/TODO.md`** with a short sketch to start the **simple** flow: an
|
|
114
|
-
agent develops your sketch into a plan —
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
agent develops your sketch into a concrete plan — deciding open points itself
|
|
115
|
+
rather than asking questions — and hands it back for you to accept as-is or
|
|
116
|
+
edit. Editing sends it round again; accepting builds the plan in one turn and
|
|
117
117
|
runs your tests (looping on failures).
|
|
118
118
|
- Create **`.gtd/REQUIREMENTS.md`** to start the **advanced** flow: two-phase
|
|
119
|
-
product then technical Q&A (`.gtd/REQUIREMENTS.md` → `.gtd/ARCHITECTURE.md`)
|
|
119
|
+
product then technical Q&A (`.gtd/REQUIREMENTS.md` → `.gtd/ARCHITECTURE.md`) —
|
|
120
|
+
each open question offers a couple of candidate answers plus a
|
|
121
|
+
`- [ ] _your answer_` slot, and you tick exactly one per question (the gate
|
|
122
|
+
won't let a phase advance while any question is unanswered) — then
|
|
120
123
|
decomposition into work **packages** (each a set of independent tasks a single
|
|
121
124
|
build turn fans out to parallel subagents), a per-package test loop, and a
|
|
122
125
|
per-package **agentic review** that verifies the package against its spec.
|
|
@@ -168,8 +171,8 @@ points at (at its `#line`), symbols over a `qa`-mode file's open questions,
|
|
|
168
171
|
diagnostics for both (live as you edit), and a `gtd.openSteeringFile` command
|
|
169
172
|
that jumps to the current state's steering file. Config-driven via each state's
|
|
170
173
|
`file:`/`mode:` (see [CLI reference](docs/cli.md#gtd-lsp)) — falls back to
|
|
171
|
-
basename dispatch (`
|
|
172
|
-
|
|
174
|
+
basename dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and
|
|
175
|
+
`review` are gtd's built-in steering-file MODES — validators, not formatters: a
|
|
173
176
|
mode's `format:` and `validate:` are shell commands a workflow (or a project's
|
|
174
177
|
`.gtdrc`) declares for itself, so you bring your own formatter and your own
|
|
175
178
|
checkers (see
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189105,6 +189105,8 @@ const isReviewWindowState = (def, state) => def.states[state]?.reviewWindow ===
|
|
|
189105
189105
|
const isReviewBaseState = (def, state) => def.states[state]?.reviewBase === true;
|
|
189106
189106
|
/** True when a step at `state` must be refused if its only change is deleting the state's `file:` (see `StateDef.requireProgress`). Safe for an unknown state name (returns `false`). */
|
|
189107
189107
|
const isRequireProgressState = (def, state) => def.states[state]?.requireProgress === true;
|
|
189108
|
+
/** True when a step at `state` must be refused unless every open question in its `qa`-mode file is answered (see `StateDef.answerGate`). Safe for an unknown state name (returns `false`). */
|
|
189109
|
+
const isAnswerGateState = (def, state) => def.states[state]?.answerGate === true;
|
|
189108
189110
|
/** The workflow's declared review-entry state name (see `StateDef.reviewEntry`) — `gtd review <commitish>` (`src/program.ts`) enters this state to start a new review process. `undefined` when no state declares one; `validateDefinition` guarantees at most one does, so the first (only) match wins. */
|
|
189109
189111
|
const reviewEntryStateOf = (def) => {
|
|
189110
189112
|
for (const [name, state] of Object.entries(def.states)) if (state.reviewEntry === true) return name;
|
|
@@ -189505,6 +189507,21 @@ const validateRequireProgress = (name, state) => {
|
|
|
189505
189507
|
if (state.file === void 0) errors.push(`state "${name}": "requireProgress" requires "file"`);
|
|
189506
189508
|
return errors;
|
|
189507
189509
|
};
|
|
189510
|
+
/**
|
|
189511
|
+
* `answerGate`, when present, is forbidden on a commit state (never at rest —
|
|
189512
|
+
* same rule family as `reviewWindow`/`requireProgress`) and REQUIRES a `file:`:
|
|
189513
|
+
* the edge gate reads that file's open questions to check every one is answered,
|
|
189514
|
+
* so a state with no `file:` to name has nothing to gate. The gate only ACTS
|
|
189515
|
+
* when the state also declares `mode: qa` (the built-in checkbox format), but
|
|
189516
|
+
* that pairing is an edge concern, not enforced here.
|
|
189517
|
+
*/
|
|
189518
|
+
const validateAnswerGate = (name, state) => {
|
|
189519
|
+
if (state.answerGate === void 0) return [];
|
|
189520
|
+
const errors = [];
|
|
189521
|
+
if (isCommitState(state)) errors.push(`state "${name}": a commit state cannot declare "answerGate"`);
|
|
189522
|
+
if (state.file === void 0) errors.push(`state "${name}": "answerGate" requires "file"`);
|
|
189523
|
+
return errors;
|
|
189524
|
+
};
|
|
189508
189525
|
/** At most one state may declare `reviewEntry: true` — `gtd review <commitish>` (`src/program.ts`) needs a single, unambiguous state name to enter (see `reviewEntryStateOf`). */
|
|
189509
189526
|
const validateReviewEntryUniqueness = (def, names) => {
|
|
189510
189527
|
const entryNames = names.filter((name) => def.states[name].reviewEntry === true);
|
|
@@ -189586,7 +189603,8 @@ const validateState = (def, name, names) => {
|
|
|
189586
189603
|
...validateReviewWindow(name, state),
|
|
189587
189604
|
...validateReviewEntry(name, state),
|
|
189588
189605
|
...validateFixEntry(name, state),
|
|
189589
|
-
...validateRequireProgress(name, state)
|
|
189606
|
+
...validateRequireProgress(name, state),
|
|
189607
|
+
...validateAnswerGate(name, state)
|
|
189590
189608
|
];
|
|
189591
189609
|
};
|
|
189592
189610
|
/**
|
|
@@ -189838,7 +189856,8 @@ const KNOWN_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
|
189838
189856
|
"reviewBase",
|
|
189839
189857
|
"reviewEntry",
|
|
189840
189858
|
"fixEntry",
|
|
189841
|
-
"requireProgress"
|
|
189859
|
+
"requireProgress",
|
|
189860
|
+
"answerGate"
|
|
189842
189861
|
]);
|
|
189843
189862
|
const KNOWN_TOP_KEYS = /* @__PURE__ */ new Set([
|
|
189844
189863
|
"vars",
|
|
@@ -190052,7 +190071,7 @@ const compileMode = (raw, name, errors) => {
|
|
|
190052
190071
|
/** The `initial` field: `true` only when the raw value is the literal boolean `true`. */
|
|
190053
190072
|
const compileInitial = (raw, name, errors) => compileBooleanFlag(raw, "initial", name, errors);
|
|
190054
190073
|
/**
|
|
190055
|
-
* A boolean state flag (`initial`/`reviewWindow`/`reviewBase`/`reviewEntry`/`fixEntry`): `true` only
|
|
190074
|
+
* A boolean state flag (`initial`/`reviewWindow`/`reviewBase`/`reviewEntry`/`fixEntry`/`requireProgress`/`answerGate`): `true` only
|
|
190056
190075
|
* when the raw value is the literal `true`; a non-boolean is a config error;
|
|
190057
190076
|
* `false` (or absent) compiles away to `undefined` so it never lands in the
|
|
190058
190077
|
* `StateDef` — `false` and "unset" mean the same thing for every such flag.
|
|
@@ -190094,7 +190113,8 @@ const assembleStateDef = (parts) => ({
|
|
|
190094
190113
|
reviewBase: parts.reviewBase,
|
|
190095
190114
|
reviewEntry: parts.reviewEntry,
|
|
190096
190115
|
fixEntry: parts.fixEntry,
|
|
190097
|
-
requireProgress: parts.requireProgress
|
|
190116
|
+
requireProgress: parts.requireProgress,
|
|
190117
|
+
answerGate: parts.answerGate
|
|
190098
190118
|
}),
|
|
190099
190119
|
...assembleContentFields(parts.content)
|
|
190100
190120
|
});
|
|
@@ -190120,7 +190140,8 @@ const compileState = (name, raw, configDir, errors, inlineFileRefs) => {
|
|
|
190120
190140
|
reviewBase: compileBooleanFlag(raw, "reviewBase", name, errors),
|
|
190121
190141
|
reviewEntry: compileBooleanFlag(raw, "reviewEntry", name, errors),
|
|
190122
190142
|
fixEntry: compileBooleanFlag(raw, "fixEntry", name, errors),
|
|
190123
|
-
requireProgress: compileBooleanFlag(raw, "requireProgress", name, errors)
|
|
190143
|
+
requireProgress: compileBooleanFlag(raw, "requireProgress", name, errors),
|
|
190144
|
+
answerGate: compileBooleanFlag(raw, "answerGate", name, errors)
|
|
190124
190145
|
});
|
|
190125
190146
|
};
|
|
190126
190147
|
/**
|
|
@@ -191537,7 +191558,7 @@ var WorktreeReader = class WorktreeReader extends Tag("WorktreeReader")() {
|
|
|
191537
191558
|
};
|
|
191538
191559
|
//#endregion
|
|
191539
191560
|
//#region src/workflows/unified.yaml
|
|
191540
|
-
var unified_default = "# The unified gtd workflow template — written verbatim into `.gtdrc.json`\n# (nested under a `workflow:` key) by `gtd init` (no argument). gtd ships no\n# default workflow: a repo picks this bundled template with `gtd init`, and a\n# state command run with no workflow configured fails (see src/Config.ts).\n# See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `gtd init` DERIVES the scaffolded\n# `.gtdrc.json` from this inline source (`renderInitScaffold`): each agent\n# state's `prompt:` is written out as an editable `gtd-prompts/<state>.md`\n# file; human `message:`/check `script:`/the `done` `commit:` body stay inline.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# KNOWN LIMITATION: `on` pattern keys are NOT Eta templates — they keep the\n# LITERAL `.gtd/...` paths matching these vars' DEFAULT values. Repointing a\n# file var changes what templates read/write WITHOUT changing what the `on`\n# patterns match, desyncing the machine (see docs/configuration.md).\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_VAR_plannerModel` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot planning/architecting/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `.gtd/TODO.md` — a short sketch — starts the SIMPLE flow (plan, build,\n review).\n - `.gtd/REQUIREMENTS.md` — the product requirements — starts the ADVANCED\n flow (product + technical Q&A, package decomposition, parallel build,\n per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* .gtd/REQUIREMENTS.md\":\n to: adv-start-check\n describe: >-\n Save the product requirements to `.gtd/REQUIREMENTS.md` to start the\n ADVANCED flow — gtd first checks the test baseline is green, then the\n agent develops them through product then technical Q&A, decomposes the\n work into packages, and builds each in parallel (**adv-start-check**).\n \"* **\":\n to: start-check\n describe: >-\n Save a short sketch to `.gtd/TODO.md` (or any other change) to start\n the SIMPLE flow — gtd first checks the test baseline is green, then the\n agent reads it, explores the codebase, and develops it into a concrete\n implementation plan (**start-check**).\n\n # ── Green-baseline gate: simple flow ──────────────────────────────────────\n # Runs the suite before any planning starts. Its script is `checking`'s test\n # wrapper verbatim (red -> .gtd/FEEDBACK.md, green -> remove it); the `on`\n # rules turn that into \"halt at start-blocked\" vs \"proceed to grilling\".\n start-check:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": start-blocked\n \"M .gtd/FEEDBACK.md\": start-blocked\n \"D .gtd/FEEDBACK.md\": grilling\n \"C\": grilling\n\n start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # ── Simple flow: one Q&A loop, monolithic build ───────────────────────────\n grilling:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete implementation plan.\n\n For every question you cannot settle yourself, add it under a\n `## Open Questions` heading near the top, one `### <question>`\n sub-heading each, its body your suggested answer in plain prose (always\n suggest one — the human accepts it by leaving it untouched, or overrides\n it by editing it). Omit the section entirely once there are none.\n\n If `<%= it.vars.todoFile %>` already carries a `## Open Questions`\n section from a previous lap, the human has now responded to that whole\n batch: fold every one of those questions into the plan — the human's edit\n where they gave one, your own suggestion where they left it untouched (an\n untouched suggestion IS acceptance) — and MOVE each resolved\n `### <question>` block down into a `## Answered Questions` section at the\n bottom. Raise any genuinely new questions under `## Open Questions`.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": grilling-answer\n\n grilling-answer:\n actor: human\n file: <%= it.vars.todoFile %>\n mode: qa\n message: |\n `<%= it.vars.todoFile %>` holds the plan under development, with any\n open questions under `## Open Questions` — each carrying a suggested\n answer. Answer a question by editing its body in place; leave a\n suggestion untouched to accept it.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: building\n describe: >-\n Change nothing to accept every suggested answer as-is and move on to\n implementation (**building**).\n \"* **\":\n to: grilling\n describe: >-\n Edit the plan — answer a question, add detail, or raise a new one — to\n send it back for another round of grilling (**grilling**).\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once its\n work is complete.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": checking\n\n # ── Green-baseline gate: advanced flow ────────────────────────────────────\n adv-start-check:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": adv-start-blocked\n \"M .gtd/FEEDBACK.md\": adv-start-blocked\n \"D .gtd/FEEDBACK.md\": adv-grilling\n \"C\": adv-grilling\n\n adv-start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: adv-start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # ── Advanced flow: product + technical Q&A ────────────────────────────────\n adv-grilling:\n actor: agent\n file: <%= it.vars.requirementsFile %>\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete, PRODUCT-level plan in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every remaining open question, add it under a `## Open Questions`\n heading near the top, one `### <question>` sub-heading each, its body your\n suggested answer in plain prose (always suggest one). Omit the section\n once there are none. On a return lap, fold the whole answered batch into\n the plan and move each resolved `### <question>` block down into a\n `## Answered Questions` section at the bottom.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n on:\n \"* **\": adv-grilling-answer\n\n adv-grilling-answer:\n actor: human\n file: <%= it.vars.requirementsFile %>\n mode: qa\n message: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development, with any open questions under `## Open Questions` — each\n carrying a suggested answer. Answer a question by editing its body in\n place; leave a suggestion untouched to accept it.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: architecting\n describe: >-\n Change nothing to accept the product plan and move on to technical\n architecting (**architecting**).\n \"* **\":\n to: adv-grilling\n describe: >-\n Edit the product plan — answer a question, add detail, or raise a new\n one — to send it back for another round (**adv-grilling**).\n\n architecting:\n actor: agent\n file: <%= it.vars.architectureFile %>\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every remaining open question, add it under a `## Open Questions`\n heading exactly as the grilling phase does (one `### <question>` each, its\n body your suggested answer). Omit the section once there are none. On a\n return lap, fold the whole answered batch in and move each resolved block\n down into a `## Answered Questions` section at the bottom.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n on:\n \"* **\": architecting-answer\n\n architecting-answer:\n actor: human\n file: <%= it.vars.architectureFile %>\n mode: qa\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development, with any open questions under `## Open Questions`. Answer a\n question by editing its body in place; leave a suggestion untouched to\n accept it.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: decompose\n describe: >-\n Change nothing to accept the technical plan and move on to decomposing\n the work into packages (**decompose**).\n \"* **\":\n to: architecting\n describe: >-\n Edit the technical plan to send it back for another round\n (**architecting**).\n\n # ── Advanced flow: decomposition + per-package parallel build loop ─────────\n decompose:\n actor: agent\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except the\n package files this prompt tells you to write (under\n `<%= it.vars.packagesDir %>/`), and `<%= it.vars.architectureFile %>`,\n which it tells you to delete.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Order the packages so the\n test suite can pass again after each one lands.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* .gtd/packages/**\": picking\n\n picking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D .gtd/NEXT.md\": reviewing\n \"* .gtd/NEXT.md\": adv-building\n \"C\": reviewing\n\n adv-building:\n actor: agent\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/`; in\n particular do NOT delete the package file (the `spec-review` gate reads it\n after you) and never touch `<%= it.vars.nextFile %>` — the `picking` state\n owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"* **\": adv-checking\n\n adv-checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": adv-fixing\n \"M .gtd/FEEDBACK.md\": adv-fixing\n \"D .gtd/FEEDBACK.md\": spec-review\n \"C\": spec-review\n\n adv-fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: adv-escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": adv-checking\n\n adv-escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: adv-checking\n describe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n\n spec-review:\n actor: agent\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `fixing` cap has (see STATES.md / docs/configuration.md).\n retry:\n max: 3\n otherwise: closing\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"M .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"D .gtd/SPEC_FEEDBACK.md\": closing\n \"C\": closing\n\n spec-fix:\n actor: agent\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to address\n and then delete.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": adv-checking\n\n closing:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %>\n on:\n \"* **\": picking\n\n # ── Shared health segment (simple build, feedback loop, code-only edits) ──\n checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: checking\n describe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n\n # ── Green-baseline gate: `gtd review` entry ───────────────────────────────\n # `gtd review <commitish>` enters HERE (reviewEntry), not `reviewing`. It runs\n # the suite on HEAD first; green proceeds to `reviewing`, red halts. The\n # `Gtd-Review-Base:` trailer rides on this state's entry commit, so the review\n # window / incremental-diff machinery downstream is unchanged.\n review-start-check:\n actor: check\n reviewEntry: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": review-start-blocked\n \"M .gtd/FEEDBACK.md\": review-start-blocked\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n review-start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: review-start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-check:\n actor: check\n fixEntry: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite to find what needs fixing; a red run\n # records .gtd/FEEDBACK.md (-> fixing), a green run cleans it up (-> idle).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: agent review -> human review -> squash finale ────────────\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-start-check` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the diff below in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.startCommit %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n The diff below is `it.reviewDiff` — on a feedback round it covers ONLY the\n changes made since the previous review, so you review just what changed,\n not items already signed off. Leave `<%= it.vars.reviewFile %>`\n uncommitted and finish your turn.\n <% if (it.reviewDiff.trim()) { %>\n\n ## Full diff under review\n\n ```diff\n <%~ it.reviewDiff %>\n ```\n <% } %>\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `review-deciding`, which decides sign-off\n # vs. feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd step human`:\n\n - Tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to sign off and collapse the\n cycle into one commit (**squashing**).\n - Leave a comment to request changes — a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review-deciding** → **feedback-building**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": review-deciding\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the cycle start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any non-.gtd/ file this round. Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n mkdir -p .gtd\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n # Which non-.gtd/ files did the human hand-edit this round?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -v '^\\.gtd/')\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n notes=\"\"\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if [ \"$before\" != \"$after\" ]; then notes=1; fi\n if [ -n \"$codeFiles\" ] || [ -n \"$notes\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the diff and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions.\"\n if [ -n \"$notes\" ]; then\n echo\n echo \"## Notes the human added to REVIEW.md this round\"\n echo\n echo '```diff'\n git show HEAD --format= -- \"$reviewFile\"\n echo '```'\n fi\n if [ -n \"$codeFiles\" ]; then\n echo\n echo \"## Files the human hand-edited this round (already committed)\"\n echo\n echo '```diff'\n git show HEAD --format= -- $codeFiles\n echo '```'\n fi\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A .gtd/REVIEW_RAW.md\": feedback-collecting\n \"M .gtd/REVIEW_RAW.md\": feedback-collecting\n \"D .gtd/REVIEW.md\": squashing\n\n feedback-collecting:\n actor: agent\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewRawFile %>` (delete it when done) and\n `<%= it.vars.reviewFeedbackFile %>` (write it). Do NOT change any other\n file — you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Read the two RAW sections and apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A .gtd/REVIEW_FEEDBACK.md\": feedback-building\n \"M .gtd/REVIEW_FEEDBACK.md\": feedback-building\n\n feedback-building:\n actor: agent\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The edge\n # gate (`enforceFeedbackProgressGate` in src/program.ts) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. `.gtd/`\n holds this workflow's own state — never create, edit, or delete anything\n under `.gtd/` except `<%= it.vars.reviewFeedbackFile %>`, which this\n prompt tells you to address and then delete.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": checking\n\n squashing:\n actor: agent\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done.\n `.gtd/` holds this workflow's own state — never create, edit, or delete\n anything under `.gtd/` except `<%= it.vars.commitMsgFile %>`, which this\n prompt tells you to write.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A .gtd/COMMIT_MSG.md\": done\n \"M .gtd/COMMIT_MSG.md\": done\n\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n";
|
|
191561
|
+
var unified_default = "# The unified gtd workflow template — written verbatim into `.gtdrc.json`\n# (nested under a `workflow:` key) by `gtd init` (no argument). gtd ships no\n# default workflow: a repo picks this bundled template with `gtd init`, and a\n# state command run with no workflow configured fails (see src/Config.ts).\n# See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `gtd init` DERIVES the scaffolded\n# `.gtdrc.json` from this inline source (`renderInitScaffold`): each agent\n# state's `prompt:` is written out as an editable `gtd-prompts/<state>.md`\n# file; human `message:`/check `script:`/the `done` `commit:` body stay inline.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# KNOWN LIMITATION: `on` pattern keys are NOT Eta templates — they keep the\n# LITERAL `.gtd/...` paths matching these vars' DEFAULT values. Repointing a\n# file var changes what templates read/write WITHOUT changing what the `on`\n# patterns match, desyncing the machine (see docs/configuration.md).\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_VAR_plannerModel` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot planning/architecting/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `.gtd/TODO.md` — a short sketch — starts the SIMPLE flow (plan, build,\n review).\n - `.gtd/REQUIREMENTS.md` — the product requirements — starts the ADVANCED\n flow (product + technical Q&A, package decomposition, parallel build,\n per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* .gtd/REQUIREMENTS.md\":\n to: adv-start-check\n describe: >-\n Save the product requirements to `.gtd/REQUIREMENTS.md` to start the\n ADVANCED flow — gtd first checks the test baseline is green, then the\n agent develops them through product then technical Q&A, decomposes the\n work into packages, and builds each in parallel (**adv-start-check**).\n \"* **\":\n to: start-check\n describe: >-\n Save a short sketch to `.gtd/TODO.md` (or any other change) to start\n the SIMPLE flow — gtd first checks the test baseline is green, then the\n agent reads it, explores the codebase, and develops it into a concrete\n implementation plan (**start-check**).\n\n # ── Green-baseline gate: simple flow ──────────────────────────────────────\n # Runs the suite before any planning starts. Its script is `checking`'s test\n # wrapper verbatim (red -> .gtd/FEEDBACK.md, green -> remove it); the `on`\n # rules turn that into \"halt at start-blocked\" vs \"proceed to planning\".\n start-check:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": start-blocked\n \"M .gtd/FEEDBACK.md\": start-blocked\n \"D .gtd/FEEDBACK.md\": planning\n \"C\": planning\n\n start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # ── Simple flow: plan-iteration loop, monolithic build ────────────────────\n planning:\n actor: agent\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build — or, on a\n return lap, the plan you already wrote plus the human's edits and comments\n on it. Read it, explore the codebase, and develop it into a concrete\n implementation plan: the files to change, the approach, and the steps to\n take.\n\n Do NOT ask questions and do NOT add an \"Open Questions\" section — decide\n every open point yourself and write the plan as a set of concrete\n decisions. Where a decision is genuinely a judgement call, state the\n choice you made and, in one line, why. The human iterates on the plan by\n editing it directly, not by answering questions.\n\n On a return lap, `<%= it.vars.todoFile %>` carries the human's edits and\n inline comments on your previous plan. Fold every edit and comment into a\n revised, self-contained plan and remove the addressed comments — the plan\n is always the current, clean proposal, never a diff.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": plan-review\n\n plan-review:\n actor: human\n file: <%= it.vars.todoFile %>\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: building\n describe: >-\n Change nothing to accept the plan as-is and move on to implementation\n (**building**).\n \"* **\":\n to: planning\n describe: >-\n Edit the plan — rewrite a step, add a constraint, or leave an inline\n comment — to send it back for another round (**planning**).\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once its\n work is complete.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": checking\n\n # ── Green-baseline gate: advanced flow ────────────────────────────────────\n adv-start-check:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": adv-start-blocked\n \"M .gtd/FEEDBACK.md\": adv-start-blocked\n \"D .gtd/FEEDBACK.md\": adv-grilling\n \"C\": adv-grilling\n\n adv-start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: adv-start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # ── Advanced flow: product + technical Q&A ────────────────────────────────\n adv-grilling:\n actor: agent\n file: <%= it.vars.requirementsFile %>\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for TWO\n concrete options plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n on:\n \"* **\": adv-grilling-answer\n\n adv-grilling-answer:\n actor: human\n file: <%= it.vars.requirementsFile %>\n mode: qa\n answerGate: true\n message: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: architecting\n describe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n product plan and move on to technical architecting (**architecting**).\n \"* **\":\n to: adv-grilling\n describe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**adv-grilling**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n architecting:\n actor: agent\n file: <%= it.vars.architectureFile %>\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n on:\n \"* **\": architecting-answer\n\n architecting-answer:\n actor: human\n file: <%= it.vars.architectureFile %>\n mode: qa\n answerGate: true\n message: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: decompose\n describe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n technical plan and move on to decomposing the work into packages\n (**decompose**).\n \"* **\":\n to: architecting\n describe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**architecting**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n # ── Advanced flow: decomposition + per-package parallel build loop ─────────\n decompose:\n actor: agent\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except the\n package files this prompt tells you to write (under\n `<%= it.vars.packagesDir %>/`), and `<%= it.vars.architectureFile %>`,\n which it tells you to delete.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Order the packages so the\n test suite can pass again after each one lands.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* .gtd/packages/**\": picking\n\n picking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D .gtd/NEXT.md\": reviewing\n \"* .gtd/NEXT.md\": adv-building\n \"C\": reviewing\n\n adv-building:\n actor: agent\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/`; in\n particular do NOT delete the package file (the `spec-review` gate reads it\n after you) and never touch `<%= it.vars.nextFile %>` — the `picking` state\n owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"* **\": adv-checking\n\n adv-checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": adv-fixing\n \"M .gtd/FEEDBACK.md\": adv-fixing\n \"D .gtd/FEEDBACK.md\": spec-review\n \"C\": spec-review\n\n adv-fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: adv-escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": adv-checking\n\n adv-escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: adv-checking\n describe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n\n spec-review:\n actor: agent\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `fixing` cap has (see STATES.md / docs/configuration.md).\n retry:\n max: 3\n otherwise: closing\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"M .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"D .gtd/SPEC_FEEDBACK.md\": closing\n \"C\": closing\n\n spec-fix:\n actor: agent\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to address\n and then delete.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": adv-checking\n\n closing:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %>\n on:\n \"* **\": picking\n\n # ── Shared health segment (simple build, feedback loop, code-only edits) ──\n checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: checking\n describe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n\n # ── Green-baseline gate: `gtd review` entry ───────────────────────────────\n # `gtd review <commitish>` enters HERE (reviewEntry), not `reviewing`. It runs\n # the suite on HEAD first; green proceeds to `reviewing`, red halts. The\n # `Gtd-Review-Base:` trailer rides on this state's entry commit, so the review\n # window / incremental-diff machinery downstream is unchanged.\n review-start-check:\n actor: check\n reviewEntry: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": review-start-blocked\n \"M .gtd/FEEDBACK.md\": review-start-blocked\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n review-start-blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: review-start-check\n describe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-check:\n actor: check\n fixEntry: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite to find what needs fixing; a red run\n # records .gtd/FEEDBACK.md (-> fixing), a green run cleans it up (-> idle).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: agent review -> human review -> squash finale ────────────\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-start-check` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the diff below in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.startCommit %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n The diff below is `it.reviewDiff` — on a feedback round it covers ONLY the\n changes made since the previous review, so you review just what changed,\n not items already signed off. Leave `<%= it.vars.reviewFile %>`\n uncommitted and finish your turn.\n <% if (it.reviewDiff.trim()) { %>\n\n ## Full diff under review\n\n ```diff\n <%~ it.reviewDiff %>\n ```\n <% } %>\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `review-deciding`, which decides sign-off\n # vs. feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd step human`:\n\n - Tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to sign off and collapse the\n cycle into one commit (**squashing**).\n - Leave a comment to request changes — a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review-deciding** → **feedback-building**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": review-deciding\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the cycle start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any non-.gtd/ file this round. Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n mkdir -p .gtd\n # Hoist the paths once, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n # Which non-.gtd/ files did the human hand-edit this round?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -v '^\\.gtd/')\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n notes=\"\"\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if [ \"$before\" != \"$after\" ]; then notes=1; fi\n if [ -n \"$codeFiles\" ] || [ -n \"$notes\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the diff and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions.\"\n if [ -n \"$notes\" ]; then\n echo\n echo \"## Notes the human added to REVIEW.md this round\"\n echo\n echo '```diff'\n git show HEAD --format= -- \"$reviewFile\"\n echo '```'\n fi\n if [ -n \"$codeFiles\" ]; then\n echo\n echo \"## Files the human hand-edited this round (already committed)\"\n echo\n echo '```diff'\n git show HEAD --format= -- $codeFiles\n echo '```'\n fi\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A .gtd/REVIEW_RAW.md\": feedback-collecting\n \"M .gtd/REVIEW_RAW.md\": feedback-collecting\n \"D .gtd/REVIEW.md\": squashing\n\n feedback-collecting:\n actor: agent\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewRawFile %>` (delete it when done) and\n `<%= it.vars.reviewFeedbackFile %>` (write it). Do NOT change any other\n file — you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Read the two RAW sections and apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A .gtd/REVIEW_FEEDBACK.md\": feedback-building\n \"M .gtd/REVIEW_FEEDBACK.md\": feedback-building\n\n feedback-building:\n actor: agent\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The edge\n # gate (`enforceFeedbackProgressGate` in src/program.ts) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. `.gtd/`\n holds this workflow's own state — never create, edit, or delete anything\n under `.gtd/` except `<%= it.vars.reviewFeedbackFile %>`, which this\n prompt tells you to address and then delete.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": checking\n\n squashing:\n actor: agent\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done.\n `.gtd/` holds this workflow's own state — never create, edit, or delete\n anything under `.gtd/` except `<%= it.vars.commitMsgFile %>`, which this\n prompt tells you to write.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A .gtd/COMMIT_MSG.md\": done\n \"M .gtd/COMMIT_MSG.md\": done\n\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n";
|
|
191541
191562
|
//#endregion
|
|
191542
191563
|
//#region src/workflows/templates.ts
|
|
191543
191564
|
/**
|
|
@@ -203578,8 +203599,6 @@ function getWellformedEdit(textEdit) {
|
|
|
203578
203599
|
};
|
|
203579
203600
|
return textEdit;
|
|
203580
203601
|
}
|
|
203581
|
-
//#endregion
|
|
203582
|
-
//#region src/OpenQuestions.ts
|
|
203583
203602
|
const OPEN_QUESTIONS_HEADING = "## Open Questions";
|
|
203584
203603
|
const ANSWERED_QUESTIONS_HEADING = "## Answered Questions";
|
|
203585
203604
|
/**
|
|
@@ -203628,15 +203647,62 @@ const splitQuestionBlocks = (lines, start) => {
|
|
|
203628
203647
|
}
|
|
203629
203648
|
return blocks;
|
|
203630
203649
|
};
|
|
203650
|
+
/** A markdown task-list checkbox line: `- [ ]` / `- [x]` / `* [X]`, optional leading indent, optional trailing text. */
|
|
203651
|
+
const CHECKBOX_RE = /^\s*[-*]\s*\[([ xX])\]\s?(.*)$/;
|
|
203652
|
+
/**
|
|
203653
|
+
* Extracts the checkbox options from a question block's body, in document
|
|
203654
|
+
* order. The LAST option is the free-text slot (`freeText: true`); its text is
|
|
203655
|
+
* normalized to `""` when it still carries the unfilled `FREE_TEXT_PLACEHOLDER`.
|
|
203656
|
+
* `bodyStart` is the absolute 0-based line index of `body[0]` (the line right
|
|
203657
|
+
* after the `###` heading), so each option carries its true source line.
|
|
203658
|
+
*/
|
|
203659
|
+
const parseOptions = (body, bodyStart) => {
|
|
203660
|
+
const raw = [];
|
|
203661
|
+
body.forEach((line, i) => {
|
|
203662
|
+
const match = CHECKBOX_RE.exec(line);
|
|
203663
|
+
if (!match) return;
|
|
203664
|
+
raw.push({
|
|
203665
|
+
checked: match[1] !== " ",
|
|
203666
|
+
text: match[2].trim(),
|
|
203667
|
+
sourceLine: bodyStart + i
|
|
203668
|
+
});
|
|
203669
|
+
});
|
|
203670
|
+
const lastIndex = raw.length - 1;
|
|
203671
|
+
return raw.map((option, i) => {
|
|
203672
|
+
const freeText = i === lastIndex;
|
|
203673
|
+
const normalized = freeText && option.text.trim().toLowerCase() === "_your answer_" ? "" : option.text;
|
|
203674
|
+
return {
|
|
203675
|
+
checked: option.checked,
|
|
203676
|
+
text: normalized,
|
|
203677
|
+
freeText,
|
|
203678
|
+
sourceLine: option.sourceLine
|
|
203679
|
+
};
|
|
203680
|
+
});
|
|
203681
|
+
};
|
|
203682
|
+
/**
|
|
203683
|
+
* An OPEN question is answered iff EXACTLY ONE option is ticked and — when that
|
|
203684
|
+
* option is the free-text slot — its (placeholder-normalized) text is non-empty.
|
|
203685
|
+
* Zero ticks (unanswered), two+ ticks (ambiguous), or a ticked-but-empty
|
|
203686
|
+
* free-text slot all read as not answered.
|
|
203687
|
+
*/
|
|
203688
|
+
const isAnswered = (options) => {
|
|
203689
|
+
const ticked = options.filter((o) => o.checked);
|
|
203690
|
+
if (ticked.length !== 1) return false;
|
|
203691
|
+
const chosen = ticked[0];
|
|
203692
|
+
return !(chosen.freeText && chosen.text.length === 0);
|
|
203693
|
+
};
|
|
203631
203694
|
/** Parses one question block into a well-formed `OpenQuestion`, or an error message. */
|
|
203632
203695
|
const parseQuestionBlock = (block, status) => {
|
|
203633
203696
|
if (block.question.length === 0) return { error: "An '### ' question heading under '## Open Questions' or '## Answered Questions' has no question text" };
|
|
203634
203697
|
const firstNonBlank = block.body.map((line) => line.trim()).find((line) => line.length > 0) ?? "";
|
|
203698
|
+
const options = status === "open" ? parseOptions(block.body, block.headingLine + 1) : [];
|
|
203635
203699
|
return {
|
|
203636
203700
|
question: block.question,
|
|
203637
203701
|
status,
|
|
203638
203702
|
text: firstNonBlank,
|
|
203639
|
-
headingLine: block.headingLine
|
|
203703
|
+
headingLine: block.headingLine,
|
|
203704
|
+
options,
|
|
203705
|
+
answered: status === "open" && options.length > 0 && isAnswered(options)
|
|
203640
203706
|
};
|
|
203641
203707
|
};
|
|
203642
203708
|
/**
|
|
@@ -203821,8 +203887,11 @@ const parseReviewDoc = (content) => {
|
|
|
203821
203887
|
* declaring state wins a path conflict, logged as a warning. Config is
|
|
203822
203888
|
* (re)loaded lazily, fresh per request (no watcher, no cache — v1). A path
|
|
203823
203889
|
* this map doesn't cover (or no config at all) falls back to today's basename
|
|
203824
|
-
* dispatch (`
|
|
203825
|
-
*
|
|
203890
|
+
* dispatch (`REVIEW.md` → `review`), so the server still works standalone with
|
|
203891
|
+
* no `.gtdrc` in sight. (`.gtd/TODO.md` is NOT dispatched by basename — the
|
|
203892
|
+
* bundled template's simple flow iterates on a free-form plan there, not a
|
|
203893
|
+
* `qa`-format file; a custom workflow that wants qa validation on TODO.md
|
|
203894
|
+
* declares it with `file:`+`mode: qa`, which the config-driven map covers.)
|
|
203826
203895
|
*
|
|
203827
203896
|
* KNOWN LIMITATION — this server understands only the two BUILT-IN modes
|
|
203828
203897
|
* (`qa`/`review`, whose parsers it owns). A workflow-declared mode (a `modes:`
|
|
@@ -203865,20 +203934,37 @@ const spanRange = (lines, startLine, endLine) => ({
|
|
|
203865
203934
|
character: (lines[endLine] ?? "").length
|
|
203866
203935
|
}
|
|
203867
203936
|
});
|
|
203868
|
-
|
|
203869
|
-
|
|
203937
|
+
/**
|
|
203938
|
+
* The outline marker for one question. Answered-section questions are
|
|
203939
|
+
* `[answered]`. An OPEN question is `[answered]` once exactly one option is
|
|
203940
|
+
* ticked (see `OpenQuestion.answered`) and `[unanswered]` otherwise — so the
|
|
203941
|
+
* `[unanswered]` entries are exactly the questions still blocking the
|
|
203942
|
+
* answer-completeness gate, navigable straight from the outline.
|
|
203943
|
+
*/
|
|
203944
|
+
const statusMarker = (question) => {
|
|
203945
|
+
if (question.status === "answered") return "[answered]";
|
|
203946
|
+
return question.answered ? "[answered]" : "[unanswered]";
|
|
203947
|
+
};
|
|
203948
|
+
/** Document symbols for a `qa`-mode file's open/answered questions (each option a navigable child of an open question). */
|
|
203870
203949
|
const questionSymbols = (content) => {
|
|
203871
203950
|
const { questions } = parseOpenQuestions(content);
|
|
203872
203951
|
const lines = content.split(/\r?\n/);
|
|
203873
203952
|
return questions.map((question, i) => {
|
|
203874
203953
|
const start = question.headingLine;
|
|
203875
203954
|
const end = Math.max(start, (questions[i + 1]?.headingLine ?? lines.length) - 1);
|
|
203955
|
+
const children = question.options.map((option) => ({
|
|
203956
|
+
name: `${option.checked ? "[x]" : "[ ]"} ${option.text || "your answer"}`,
|
|
203957
|
+
kind: import_main.SymbolKind.Boolean,
|
|
203958
|
+
range: lineRange(lines, option.sourceLine),
|
|
203959
|
+
selectionRange: lineRange(lines, option.sourceLine)
|
|
203960
|
+
}));
|
|
203876
203961
|
return {
|
|
203877
|
-
name: `${statusMarker(question
|
|
203962
|
+
name: `${statusMarker(question)} ${question.question}`,
|
|
203878
203963
|
detail: question.text,
|
|
203879
203964
|
kind: import_main.SymbolKind.Boolean,
|
|
203880
203965
|
range: spanRange(lines, start, end),
|
|
203881
|
-
selectionRange: lineRange(lines, start)
|
|
203966
|
+
selectionRange: lineRange(lines, start),
|
|
203967
|
+
...children.length > 0 ? { children } : {}
|
|
203882
203968
|
};
|
|
203883
203969
|
});
|
|
203884
203970
|
};
|
|
@@ -204009,6 +204095,67 @@ const reviewCodeActions = (uri, content, range) => {
|
|
|
204009
204095
|
});
|
|
204010
204096
|
return actions;
|
|
204011
204097
|
};
|
|
204098
|
+
/** Flips the `[ ]`/`[x]` box of the checkbox on line `line`, preserving the rest of the line exactly. `undefined` when the line has no checkbox. */
|
|
204099
|
+
const toggleOptionEdit = (content, line) => {
|
|
204100
|
+
const raw = content.split(/\r?\n/)[line];
|
|
204101
|
+
if (raw === void 0) return void 0;
|
|
204102
|
+
const match = /\[([ xX])\]/.exec(raw);
|
|
204103
|
+
if (!match) return void 0;
|
|
204104
|
+
const character = match.index + 1;
|
|
204105
|
+
return {
|
|
204106
|
+
range: {
|
|
204107
|
+
start: {
|
|
204108
|
+
line,
|
|
204109
|
+
character
|
|
204110
|
+
},
|
|
204111
|
+
end: {
|
|
204112
|
+
line,
|
|
204113
|
+
character: character + 1
|
|
204114
|
+
}
|
|
204115
|
+
},
|
|
204116
|
+
newText: match[1] === " " ? "x" : " "
|
|
204117
|
+
};
|
|
204118
|
+
};
|
|
204119
|
+
/** The edits that make `option` the sole ticked option in `question` (radio semantics): check it, and uncheck any already-ticked sibling — so the question ends with exactly one tick (what the completeness gate wants). */
|
|
204120
|
+
const pickOptionEdits = (content, question, option) => {
|
|
204121
|
+
const edits = [];
|
|
204122
|
+
for (const sibling of question.options) {
|
|
204123
|
+
if (sibling.sourceLine !== option.sourceLine && !sibling.checked) continue;
|
|
204124
|
+
const edit = toggleOptionEdit(content, sibling.sourceLine);
|
|
204125
|
+
if (edit) edits.push(edit);
|
|
204126
|
+
}
|
|
204127
|
+
return edits;
|
|
204128
|
+
};
|
|
204129
|
+
/** The single code action for the option line the cursor sits on: uncheck it if ticked, else pick it (radio). `undefined` when there is no edit to make. */
|
|
204130
|
+
const optionCodeAction = (uri, content, question, option) => {
|
|
204131
|
+
const edits = option.checked ? [toggleOptionEdit(content, option.sourceLine)].filter((e) => e !== void 0) : pickOptionEdits(content, question, option);
|
|
204132
|
+
if (edits.length === 0) return void 0;
|
|
204133
|
+
return {
|
|
204134
|
+
title: option.checked ? "gtd: uncheck this option" : "gtd: pick this option",
|
|
204135
|
+
kind: import_main.CodeActionKind.QuickFix,
|
|
204136
|
+
edit: { changes: { [uri]: edits } }
|
|
204137
|
+
};
|
|
204138
|
+
};
|
|
204139
|
+
/**
|
|
204140
|
+
* Code actions for a `qa`-mode file: on an open question's option line, "pick
|
|
204141
|
+
* this option" (radio semantics — check it and uncheck every sibling in the
|
|
204142
|
+
* same question, so exactly one stays ticked) or "uncheck this option" when it
|
|
204143
|
+
* is already the chosen one. No action off an option line, or on an
|
|
204144
|
+
* answered-section (prose) question.
|
|
204145
|
+
*/
|
|
204146
|
+
const questionCodeActions = (uri, content, range) => {
|
|
204147
|
+
const { questions } = parseOpenQuestions(content);
|
|
204148
|
+
const cursorLine = range.start.line;
|
|
204149
|
+
const actions = [];
|
|
204150
|
+
for (const question of questions) {
|
|
204151
|
+
if (question.status !== "open") continue;
|
|
204152
|
+
const option = question.options.find((o) => o.sourceLine === cursorLine);
|
|
204153
|
+
if (!option) continue;
|
|
204154
|
+
const action = optionCodeAction(uri, content, question, option);
|
|
204155
|
+
if (action) actions.push(action);
|
|
204156
|
+
}
|
|
204157
|
+
return actions;
|
|
204158
|
+
};
|
|
204012
204159
|
/**
|
|
204013
204160
|
* Go-to-definition target for a `.gtd/REVIEW.md` hunk line: the file the hunk
|
|
204014
204161
|
* pointer at `line` (0-based, `params.position.line`) points into. `root` is
|
|
@@ -204043,9 +204190,8 @@ const hunkDefinitionLocation = (content, line, root) => {
|
|
|
204043
204190
|
}
|
|
204044
204191
|
};
|
|
204045
204192
|
};
|
|
204046
|
-
/** The basename dispatch this server has always had — the fallback for any path the active workflow's `file:` map doesn't cover (or when no config resolves at all). */
|
|
204193
|
+
/** The basename dispatch this server has always had — the fallback for any path the active workflow's `file:` map doesn't cover (or when no config resolves at all). `TODO.md` is intentionally NOT mapped: the bundled template's simple flow now iterates on a free-form plan there, not a `qa`-format file. */
|
|
204047
204194
|
const basenameFallbackMode = (name) => {
|
|
204048
|
-
if (name === "TODO.md") return "qa";
|
|
204049
204195
|
if (name === "REVIEW.md") return "review";
|
|
204050
204196
|
};
|
|
204051
204197
|
/**
|
|
@@ -204234,8 +204380,11 @@ const startLspServer = () => gen(function* () {
|
|
|
204234
204380
|
const document = documents.get(params.textDocument.uri);
|
|
204235
204381
|
if (!document) return [];
|
|
204236
204382
|
const map = await loadModeMap(rootFor(document.uri), warn);
|
|
204237
|
-
|
|
204238
|
-
|
|
204383
|
+
switch (modeForDocument(document.uri, map)) {
|
|
204384
|
+
case "review": return reviewCodeActions(document.uri, document.getText(), params.range);
|
|
204385
|
+
case "qa": return questionCodeActions(document.uri, document.getText(), params.range);
|
|
204386
|
+
default: return [];
|
|
204387
|
+
}
|
|
204239
204388
|
});
|
|
204240
204389
|
connection.onDefinition(async (params) => {
|
|
204241
204390
|
const document = documents.get(params.textDocument.uri);
|
|
@@ -204301,7 +204450,7 @@ const startLspServer = () => gen(function* () {
|
|
|
204301
204450
|
const escapeLabel = (text) => text.replace(/"/g, "'").replace(/\r?\n/g, " ");
|
|
204302
204451
|
/**
|
|
204303
204452
|
* A Mermaid-safe node id for one state name: every non-word character folds
|
|
204304
|
-
* to `_` (state names may carry hyphens — `
|
|
204453
|
+
* to `_` (state names may carry hyphens — `plan-review`,
|
|
204305
204454
|
* `review-deciding` — which Mermaid's bare identifier grammar doesn't
|
|
204306
204455
|
* guarantee), and a leading digit gets an `s_` prefix (Mermaid ids can't
|
|
204307
204456
|
* start with one). The exact original name still reaches the diagram via
|
|
@@ -204701,6 +204850,7 @@ const stepAsActor = (invoker, cost, model) => gen(function* () {
|
|
|
204701
204850
|
yield* enforceSteeringGate(worktree.read, invoker, rest, context, decision.kind);
|
|
204702
204851
|
yield* enforceReviewSignoffGate(worktree.read, invoker, rest, context, changes, decision.kind);
|
|
204703
204852
|
yield* enforceFeedbackProgressGate(invoker, rest, context, changes, decision.kind);
|
|
204853
|
+
yield* enforceAnswerCompletenessGate(worktree.read, invoker, rest, context, decision.kind);
|
|
204704
204854
|
const outcome = yield* executeDecision(git, run, executable, context, cost, model);
|
|
204705
204855
|
return {
|
|
204706
204856
|
state: rest.state,
|
|
@@ -204998,6 +205148,53 @@ const enforceFeedbackProgressGate = (invoker, rest, context, changes, kind) => g
|
|
|
204998
205148
|
});
|
|
204999
205149
|
if (verdict.kind === "refuse") return yield* fail$2(new Error(verdict.reason));
|
|
205000
205150
|
});
|
|
205151
|
+
/** The `mode:` name of gtd's built-in open-questions checkbox format — the only mode the answer-completeness gate below acts on. */
|
|
205152
|
+
const QA_MODE = "qa";
|
|
205153
|
+
/**
|
|
205154
|
+
* The PURE core of the answer-completeness gate (see
|
|
205155
|
+
* `enforceAnswerCompletenessGate`). Parses the working-tree `content` of a
|
|
205156
|
+
* `qa`-mode file and refuses when any OPEN question is not answered — EXACTLY
|
|
205157
|
+
* ONE checkbox ticked, and (for a ticked free-text slot) non-empty text (see
|
|
205158
|
+
* `OpenQuestions.answered`). An open question with NO checkbox options is also
|
|
205159
|
+
* unanswered — a decision can't have been made on it. Zero remaining open
|
|
205160
|
+
* questions (the section was deleted, or the agent surfaced none) allows the
|
|
205161
|
+
* step: that is BOTH the tick-then-loop path and the clean advance / accept-all
|
|
205162
|
+
* escape. Kept separate from the Effect so it is directly unit-tested (see
|
|
205163
|
+
* program.test.ts).
|
|
205164
|
+
*/
|
|
205165
|
+
const classifyAnswerCompleteness = (input) => {
|
|
205166
|
+
const unanswered = parseOpenQuestions(input.content).questions.filter((q) => q.status === "open").filter((q) => !q.answered);
|
|
205167
|
+
if (unanswered.length === 0) return { kind: "allow" };
|
|
205168
|
+
const list = unanswered.map((q) => ` - ${q.question}`).join("\n");
|
|
205169
|
+
return {
|
|
205170
|
+
kind: "refuse",
|
|
205171
|
+
reason: `gtd step ${input.invoker}: ${unanswered.length} open question(s) in ${input.file} not answered at "${input.stateName}" — tick exactly one option per question (or delete a question you don't want to answer, or delete the whole "## Open Questions" section to accept the plan as-is):\n${list}`
|
|
205172
|
+
};
|
|
205173
|
+
};
|
|
205174
|
+
/**
|
|
205175
|
+
* The answer-completeness gate (edge, like the sign-off gate above). At an
|
|
205176
|
+
* `answerGate: true` state with `mode: qa` (the unified template's
|
|
205177
|
+
* `adv-grilling-answer`/`architecting-answer`), a human step is refused unless
|
|
205178
|
+
* every open question in the file is answered — the pure
|
|
205179
|
+
* `classifyAnswerCompleteness` decides over the WORKING-TREE contents. A no-op
|
|
205180
|
+
* when the state is not an answer gate (so the agent's own authoring step, which
|
|
205181
|
+
* writes all-unticked options, is never gated), the mode isn't `qa`, or the
|
|
205182
|
+
* decision is a squash/no-op.
|
|
205183
|
+
*/
|
|
205184
|
+
const enforceAnswerCompletenessGate = (worktreeRead, invoker, rest, context, kind) => gen(function* () {
|
|
205185
|
+
if (kind !== "commit") return;
|
|
205186
|
+
if (!isAnswerGateState(rest.def, rest.state) || rest.stateDef.mode !== QA_MODE) return;
|
|
205187
|
+
const file = yield* renderFile(rest.stateDef, context);
|
|
205188
|
+
if (file === void 0) return;
|
|
205189
|
+
const current = yield* try_(() => worktreeRead(file)).pipe(catchAll(() => succeed$3("")));
|
|
205190
|
+
const verdict = classifyAnswerCompleteness({
|
|
205191
|
+
file,
|
|
205192
|
+
stateName: rest.state,
|
|
205193
|
+
invoker,
|
|
205194
|
+
content: current
|
|
205195
|
+
});
|
|
205196
|
+
if (verdict.kind === "refuse") return yield* fail$2(new Error(verdict.reason));
|
|
205197
|
+
});
|
|
205001
205198
|
/**
|
|
205002
205199
|
* `gtd validate [--json]`: format (in place) then validate the steering file
|
|
205003
205200
|
* the resolved rest declares (`file:` rendered, `mode:` selecting how), over
|
package/docs/cli.md
CHANGED
|
@@ -76,7 +76,7 @@ create .gtdrc.json`.
|
|
|
76
76
|
The unified template has four entry points — each behind a green-baseline gate
|
|
77
77
|
that runs the suite before starting — into one shared review/squash tail (see
|
|
78
78
|
[STATES.md §10](../STATES.md#10-the-bundled-workflow-templates)): creating
|
|
79
|
-
`.gtd/TODO.md` starts the simple flow (
|
|
79
|
+
`.gtd/TODO.md` starts the simple flow (planning → building → checking), creating
|
|
80
80
|
`.gtd/REQUIREMENTS.md` starts the advanced flow (two-phase Q&A planning,
|
|
81
81
|
per-package parallel build, agentic spec-review), `gtd review <commitish>`
|
|
82
82
|
enters review directly, and `gtd fix` goes straight into repairing failing
|
|
@@ -126,7 +126,7 @@ to `--json`.
|
|
|
126
126
|
Plain-mode output is one line:
|
|
127
127
|
|
|
128
128
|
```
|
|
129
|
-
committed: gtd(human): idle →
|
|
129
|
+
committed: gtd(human): idle → planning
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
or, at a no-op:
|
|
@@ -142,7 +142,7 @@ not passed):
|
|
|
142
142
|
```json
|
|
143
143
|
{
|
|
144
144
|
"state": "idle",
|
|
145
|
-
"subject": "gtd(human): idle →
|
|
145
|
+
"subject": "gtd(human): idle → planning",
|
|
146
146
|
"cost": 1450,
|
|
147
147
|
"model": "claude-opus-4-8"
|
|
148
148
|
}
|
|
@@ -385,7 +385,7 @@ agent or human:
|
|
|
385
385
|
declares `file:`+`mode:` formats that file in place and validates it first
|
|
386
386
|
(with the very same mode commands), and **refuses the step** (committing
|
|
387
387
|
nothing) when it is invalid — so a human's edit at a gate (answering at
|
|
388
|
-
`grilling-answer`, reviewing at `await-review`) is formatted and checked
|
|
388
|
+
`adv-grilling-answer`, reviewing at `await-review`) is formatted and checked
|
|
389
389
|
exactly like an agent's draft, and a malformed steering file is never
|
|
390
390
|
committed.
|
|
391
391
|
|
|
@@ -409,10 +409,10 @@ output is identical regardless of the current process/branch state.
|
|
|
409
409
|
$ gtd mermaid
|
|
410
410
|
stateDiagram-v2
|
|
411
411
|
state "idle" as idle
|
|
412
|
-
state "
|
|
412
|
+
state "planning" as planning
|
|
413
413
|
...
|
|
414
414
|
[*] --> idle
|
|
415
|
-
idle -->
|
|
415
|
+
idle --> planning : * **
|
|
416
416
|
...
|
|
417
417
|
note right of idle : human · message
|
|
418
418
|
...
|
|
@@ -425,7 +425,7 @@ Mermaid-aware renderer (GitHub, GitLab, VS Code, Obsidian, the
|
|
|
425
425
|
|
|
426
426
|
State names are aliased to Mermaid-safe identifiers (non-word characters fold to
|
|
427
427
|
`_`; a digit-led name gets an `s_` prefix) via a `state "<name>" as <alias>`
|
|
428
|
-
declaration up front, so a hyphenated name like `
|
|
428
|
+
declaration up front, so a hyphenated name like `plan-review` still displays
|
|
429
429
|
with its exact declared spelling. Rejects `--json` (exit 1,
|
|
430
430
|
`gtd mermaid does not accept --json`) — there is no structured shape to emit
|
|
431
431
|
beyond the Mermaid source itself — and takes no arguments (extra positional args
|
package/docs/configuration.md
CHANGED
|
@@ -156,7 +156,7 @@ workflow:
|
|
|
156
156
|
"Delete `.gtd/REVIEW.md` to approve the whole cycle and rest at
|
|
157
157
|
idle."
|
|
158
158
|
"* **":
|
|
159
|
-
to:
|
|
159
|
+
to: planning
|
|
160
160
|
describe:
|
|
161
161
|
"Change any source file to leave feedback and start another round."
|
|
162
162
|
```
|
|
@@ -214,18 +214,18 @@ Forbidden on a commit state (never at rest):
|
|
|
214
214
|
```yaml
|
|
215
215
|
workflow:
|
|
216
216
|
states:
|
|
217
|
-
|
|
217
|
+
planning:
|
|
218
218
|
actor: agent
|
|
219
219
|
memory: plan
|
|
220
|
-
prompt: develop the
|
|
220
|
+
prompt: develop the sketch into a concrete plan
|
|
221
221
|
on:
|
|
222
|
-
"* **":
|
|
223
|
-
|
|
222
|
+
"* **": plan-review
|
|
223
|
+
plan-review:
|
|
224
224
|
actor: human
|
|
225
|
-
message:
|
|
225
|
+
message: accept the plan, or edit it to iterate
|
|
226
226
|
on:
|
|
227
227
|
"C": building
|
|
228
|
-
"* **":
|
|
228
|
+
"* **": planning
|
|
229
229
|
building:
|
|
230
230
|
actor: agent
|
|
231
231
|
memory: build # a different scope — implementation starts fresh
|
|
@@ -236,7 +236,7 @@ workflow:
|
|
|
236
236
|
commit: "feat: done"
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
-
Because `
|
|
239
|
+
Because `planning` re-enters itself around the review loop, every lap emits
|
|
240
240
|
`memory: plan` and the planner keeps its accumulated exploration; the move to
|
|
241
241
|
`building` emits a different label (`build`), which is where the driver clears
|
|
242
242
|
memory for a fresh implementation turn. A loop of same-labelled turns retains; a
|
|
@@ -282,10 +282,10 @@ workflow:
|
|
|
282
282
|
**steering-file mode**. Two names are BUILT IN, each carrying a VALIDATOR gtd
|
|
283
283
|
implements itself:
|
|
284
284
|
|
|
285
|
-
| `mode` | Format
|
|
286
|
-
| -------- |
|
|
287
|
-
| `qa` | The open-questions format (`## Open Questions` near the top and `## Answered Questions` at the bottom, one `###` sub-heading per question with a free-form body; status is positional, no marker line). |
|
|
288
|
-
| `review` | The checkbox review format (`# Review: <hash>` header, `<!-- base: <hash> -->` comment, `##` chunks, `- [ ]` pointers).
|
|
285
|
+
| `mode` | Format |
|
|
286
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| `qa` | The open-questions format (`## Open Questions` near the top and `## Answered Questions` at the bottom, one `###` sub-heading per question with a free-form body; status is positional, no marker line). The bundled advanced flow additionally writes each open question as a checkbox list (candidate answers + a `- [ ] _your answer_` slot) and gates the human's answer step with `answerGate` (exactly one tick per question) — but that convention + gate live in the workflow/edge, not in this validator, which only checks the structure above. |
|
|
288
|
+
| `review` | The checkbox review format (`# Review: <hash>` header, `<!-- base: <hash> -->` comment, `##` chunks, `- [ ]` pointers). |
|
|
289
289
|
|
|
290
290
|
Any other name must be declared in a `modes:` map (next section) — as must a
|
|
291
291
|
`format:` command for these two, since gtd ships no formatter. A `mode:` naming
|
|
@@ -752,8 +752,8 @@ The one bundled template has three entry points into a shared review/squash tail
|
|
|
752
752
|
(all walked through in
|
|
753
753
|
[STATES.md §10](../STATES.md#10-the-bundled-workflow-templates)):
|
|
754
754
|
|
|
755
|
-
- **the simple flow** — creating `.gtd/TODO.md` starts idle →
|
|
756
|
-
|
|
755
|
+
- **the simple flow** — creating `.gtd/TODO.md` starts idle → planning ⇄
|
|
756
|
+
plan-review → building → checking ⇄ fixing (→ escalate) → reviewing →
|
|
757
757
|
await-review.
|
|
758
758
|
- **the advanced flow** — creating `.gtd/REQUIREMENTS.md` starts the fuller
|
|
759
759
|
machine: two-phase Q&A planning (product then architecture), package
|
package/docs/upgrading.md
CHANGED
|
@@ -132,8 +132,8 @@ gtd init # takes no argument
|
|
|
132
132
|
`gtd init` (no argument) writes a `.gtdrc.json` for the single bundled
|
|
133
133
|
**unified** template (its agent prompts extracted to editable `gtd-prompts/*.md`
|
|
134
134
|
files the config references). The template forks on which steering file you
|
|
135
|
-
create: `.gtd/TODO.md` starts the **simple flow** (a
|
|
136
|
-
plan, direct `building`, a `checking`/`fixing` loop), while
|
|
135
|
+
create: `.gtd/TODO.md` starts the **simple flow** (a `planning` ⇄ `plan-review`
|
|
136
|
+
plan-iteration loop, direct `building`, a `checking`/`fixing` loop), while
|
|
137
137
|
`.gtd/REQUIREMENTS.md` starts the **advanced flow** (two-phase grilling ⇄
|
|
138
138
|
grilling-answer / architecting ⇄ architecting-answer / decompose / picking /
|
|
139
139
|
per-package build with an agentic spec-review gate). Both converge on the same
|