@pmelab/gtd 7.1.0 → 7.2.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 +18 -2
- package/bin/gtd-loop +33 -0
- package/dist/gtd.bundle.mjs +326 -56
- package/docs/cli.md +120 -2
- package/docs/configuration.md +36 -3
- package/docs/loop.md +53 -3
- package/docs/upgrading.md +36 -0
- package/package.json +1 -1
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189424,6 +189424,13 @@ const validateMemory = (name, state) => {
|
|
|
189424
189424
|
if (isCommitState(state) && state.memory !== void 0) errors.push(`state "${name}": a commit state cannot declare "memory"`);
|
|
189425
189425
|
return errors;
|
|
189426
189426
|
};
|
|
189427
|
+
/** `label`, when present, must be a non-empty string; forbidden on a commit state — same rule family as `model`/`memory` (`validateModel`/`validateMemory`): a commit state is never at rest and emits nothing for a driver/viewer to display a label for. */
|
|
189428
|
+
const validateLabel = (name, state) => {
|
|
189429
|
+
const errors = [];
|
|
189430
|
+
if (state.label !== void 0 && state.label === "") errors.push(`state "${name}": "label" must be a non-empty string`);
|
|
189431
|
+
if (isCommitState(state) && state.label !== void 0) errors.push(`state "${name}": a commit state cannot declare "label"`);
|
|
189432
|
+
return errors;
|
|
189433
|
+
};
|
|
189427
189434
|
/**
|
|
189428
189435
|
* The `modes:` map itself: every declared mode must carry at least one of
|
|
189429
189436
|
* `format`/`validate`, and neither may be blank (a whitespace-only shell
|
|
@@ -189618,6 +189625,7 @@ const validateState = (def, name, names) => {
|
|
|
189618
189625
|
...validateRetry(name, state, names),
|
|
189619
189626
|
...validateModel(name, state),
|
|
189620
189627
|
...validateMemory(name, state),
|
|
189628
|
+
...validateLabel(name, state),
|
|
189621
189629
|
...validateFile(name, state),
|
|
189622
189630
|
...validateMode(def, name, state),
|
|
189623
189631
|
...validateReviewWindow(name, state),
|
|
@@ -189640,7 +189648,9 @@ const validateState = (def, name, names) => {
|
|
|
189640
189648
|
* non-negative integer; `model`, when present, is a non-empty string and is
|
|
189641
189649
|
* never declared on a commit state; `memory`, when present, is a non-empty
|
|
189642
189650
|
* string and is never declared on a commit state (same rule family as
|
|
189643
|
-
* `model`); `
|
|
189651
|
+
* `model`); `label`, when present, is a non-empty string and is never
|
|
189652
|
+
* declared on a commit state (same rule family as `model`); `file`, when
|
|
189653
|
+
* present, is a non-empty
|
|
189644
189654
|
* string and is never declared on a commit state; `mode`, when present, names
|
|
189645
189655
|
* a mode the definition knows (a built-in or a `modes:` entry — see
|
|
189646
189656
|
* `knownModes`), requires a sibling `file`, and is never declared on a commit
|
|
@@ -190018,6 +190028,7 @@ const collectGroups = (raw) => {
|
|
|
190018
190028
|
* otherwise: <targetState>
|
|
190019
190029
|
* model: <string> # optional, opaque harness hint — never on a commit state
|
|
190020
190030
|
* memory: <string> # optional, opaque memory-scope label — never on a commit state
|
|
190031
|
+
* label: <string> # optional, opaque display name — never on a commit state
|
|
190021
190032
|
* file: <string> # optional, an Eta template naming the state's steering file — never on a commit state
|
|
190022
190033
|
* mode: <modeName> # optional, requires "file" — a built-in (qa/review) or a `modes:` entry; never on a commit state
|
|
190023
190034
|
* ```
|
|
@@ -190187,6 +190198,7 @@ const KNOWN_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
|
190187
190198
|
"retry",
|
|
190188
190199
|
"model",
|
|
190189
190200
|
"memory",
|
|
190201
|
+
"label",
|
|
190190
190202
|
"file",
|
|
190191
190203
|
"mode",
|
|
190192
190204
|
"reviewWindow",
|
|
@@ -190387,6 +190399,15 @@ const compileMemory = (raw, name, errors) => {
|
|
|
190387
190399
|
}
|
|
190388
190400
|
return raw.memory;
|
|
190389
190401
|
};
|
|
190402
|
+
/** The `label` field: an opaque display name, or undefined (either absent or invalid — the type mismatch is its own error). Never interpreted or validated beyond "is it a string" — see `PatternMachine.StateDef.label`. */
|
|
190403
|
+
const compileLabel = (raw, name, errors) => {
|
|
190404
|
+
if (raw.label === void 0) return void 0;
|
|
190405
|
+
if (typeof raw.label !== "string") {
|
|
190406
|
+
errors.push(`state "${name}": "label" must be a string`);
|
|
190407
|
+
return;
|
|
190408
|
+
}
|
|
190409
|
+
return raw.label;
|
|
190410
|
+
};
|
|
190390
190411
|
/** The `file` field: an Eta template string naming the state's steering file, or undefined (either absent or invalid — the type mismatch is its own error). Vocabulary/shape rules (non-empty, forbidden on a commit state) are `validateDefinition`'s concern, not this compiler's — see `PatternMachine.StateDef.file`. */
|
|
190391
190412
|
const compileFile = (raw, name, errors) => {
|
|
190392
190413
|
if (raw.file === void 0) return void 0;
|
|
@@ -190444,6 +190465,7 @@ const assembleStateDef = (parts) => ({
|
|
|
190444
190465
|
retry: parts.retry,
|
|
190445
190466
|
model: parts.model,
|
|
190446
190467
|
memory: parts.memory,
|
|
190468
|
+
label: parts.label,
|
|
190447
190469
|
file: parts.file,
|
|
190448
190470
|
mode: parts.mode,
|
|
190449
190471
|
reviewWindow: parts.reviewWindow,
|
|
@@ -190471,6 +190493,7 @@ const compileState = (name, raw, configDir, errors, inlineFileRefs) => {
|
|
|
190471
190493
|
retry: compileRetry(raw.retry, name, errors),
|
|
190472
190494
|
model: compileModel(raw, name, errors),
|
|
190473
190495
|
memory: compileMemory(raw, name, errors),
|
|
190496
|
+
label: compileLabel(raw, name, errors),
|
|
190474
190497
|
file: compileFile(raw, name, errors),
|
|
190475
190498
|
mode: compileMode(raw, name, errors),
|
|
190476
190499
|
reviewWindow: compileBooleanFlag(raw, "reviewWindow", name, errors),
|
|
@@ -190525,7 +190548,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true) =
|
|
|
190525
190548
|
};
|
|
190526
190549
|
//#endregion
|
|
190527
190550
|
//#region src/workflows/unified.yaml
|
|
190528
|
-
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this inline source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts\n# and STATES.md). Expansion is a compile-time raw → raw pre-pass with NO runtime\n# footprint: the pure engine only ever sees the flat concrete states, and each\n# invocation's `as:` rename map reproduces the exact state names the flat\n# template used — so the compiled `WorkflowDefinition` is BYTE-IDENTICAL to the\n# frozen `unified.flat.yaml` (pinned by src/workflows/golden.test.ts). The\n# sub-machines split into DEDUP (a gate/loop authored once, invoked several\n# times) and ENCAPSULATION (a single-instance cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (adv-grilling) and technical (architecting)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# 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\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records .gtd/FEEDBACK.md, a green run cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (adv-checking/adv-fixing/adv-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe]\n states:\n check:\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\": fix\n \"M .gtd/FEEDBACK.md\": fix\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n fix:\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 \"* **\": check\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: check\n describe: $escalateDescribe\n\n # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> .gtd/FEEDBACK.md, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, checkScript, blockedMessage, blockedDescribe]\n states:\n check:\n actor: check\n script: $checkScript\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (adv-grilling) and\n # technical (architecting) phases of the advanced flow.\n qaLoop:\n params: [authorFile, authorPrompt, answerMessage, onAdvance, advanceDescribe, editDescribe]\n states:\n author:\n actor: agent\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n describe: $advanceDescribe\n \"* **\":\n to: author\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n 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: $onAccept\n describe: >-\n Change nothing to accept the plan as-is and move on to implementation\n (**building**).\n \"* **\":\n to: planning\n describe: >-\n Edit the plan — rewrite a step, add a constraint, or leave an inline\n comment — to send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n 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\": $onSignoff\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 # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\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: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"M .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\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 # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\n states:\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\": $onDrained\n \"* .gtd/NEXT.md\": adv-building\n \"C\": $onDrained\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 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\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n # Advanced package health triple: adv-checking/adv-fixing/adv-escalate -> spec-review.\n - submachine: makeGreen\n name: pkg\n as: { check: adv-checking, fix: adv-fixing, escalate: adv-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: start\n as: { check: start-check, blocked: start-blocked }\n with:\n onGreen: planning\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: adv-start\n as: { check: adv-start-check, blocked: adv-start-blocked }\n with:\n onGreen: adv-grilling\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review-start\n as: { check: review-start-check, blocked: review-start-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n\n # Advanced flow: product Q&A loop (adv-grilling/adv-grilling-answer -> architecting).\n - submachine: qaLoop\n name: grill\n as: { author: adv-grilling, answer: adv-grilling-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: architecting\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for TWO\n concrete options plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n product plan and move on to technical architecting (**architecting**).\n editDescribe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**adv-grilling**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n # Advanced flow: technical Q&A loop (architecting/architecting-answer -> decompose).\n - submachine: qaLoop\n name: arch\n as: { author: architecting, answer: architecting-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n technical plan and move on to decomposing the work into packages\n (**decompose**).\n editDescribe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**architecting**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n\n # Simple flow plan-iteration loop (planning/plan-review -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/adv-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n # 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 # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\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 # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\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 # ── 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: squash finale (review loop is the `humanReview` sub-machine) ─\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";
|
|
190551
|
+
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this inline source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts\n# and STATES.md). Expansion is a compile-time raw → raw pre-pass with NO runtime\n# footprint: the pure engine only ever sees the flat concrete states, and each\n# invocation's `as:` rename map reproduces the exact state names the flat\n# template used — so the compiled `WorkflowDefinition` is BYTE-IDENTICAL to the\n# frozen `unified.flat.yaml` (pinned by src/workflows/golden.test.ts). The\n# sub-machines split into DEDUP (a gate/loop authored once, invoked several\n# times) and ENCAPSULATION (a single-instance cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (adv-grilling) and technical (architecting)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-start-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * .gtd/REQUIREMENTS.md -> `adv-start-check` -> the ADVANCED flow (product +\n# technical Q&A, package decomposition, per-package parallel build +\n# agentic spec-review).\n# * anything else (e.g. .gtd/TODO.md) -> `start-check` -> the SIMPLE flow (one\n# grilling Q&A loop, a monolithic build, no decomposition, no agentic\n# review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-start-check`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-check`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`adv-checking`/`adv-fixing`/`adv-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# 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\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records .gtd/FEEDBACK.md, a green run cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (adv-checking/adv-fixing/adv-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe, checkLabel, fixLabel, escalateLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fix\n \"M .gtd/FEEDBACK.md\": fix\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n fix:\n actor: agent\n label: $fixLabel\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": check\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n describe: $escalateDescribe\n\n # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> .gtd/FEEDBACK.md, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, checkScript, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n script: $checkScript\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (adv-grilling) and\n # technical (architecting) phases of the advanced flow.\n qaLoop:\n params:\n [\n authorFile,\n authorPrompt,\n answerMessage,\n onAdvance,\n advanceDescribe,\n editDescribe,\n authorLabel,\n answerLabel,\n ]\n states:\n author:\n actor: agent\n label: $authorLabel\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n label: $answerLabel\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n describe: $advanceDescribe\n \"* **\":\n to: author\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build — or, on a\n return lap, the plan you already wrote plus the human's edits and comments\n on it. Read it, explore the codebase, and develop it into a concrete\n implementation plan: the files to change, the approach, and the steps to\n take.\n\n Do NOT ask questions and do NOT add an \"Open Questions\" section — decide\n every open point yourself and write the plan as a set of concrete\n decisions. Where a decision is genuinely a judgement call, state the\n choice you made and, in one line, why. The human iterates on the plan by\n editing it directly, not by answering questions.\n\n On a return lap, `<%= it.vars.todoFile %>` carries the human's edits and\n inline comments on your previous plan. Fold every edit and comment into a\n revised, self-contained plan and remove the addressed comments — the plan\n is always the current, clean proposal, never a diff.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": plan-review\n\n plan-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n describe: >-\n Change nothing to accept the plan as-is and move on to implementation\n (**building**).\n \"* **\":\n to: planning\n describe: >-\n Edit the plan — rewrite a step, add a constraint, or leave an inline\n comment — to send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-start-check` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` 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 label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `review-deciding`, which decides sign-off\n # vs. feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n When you've been through the whole diff, run `gtd step human`:\n\n - Tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to sign off and collapse the\n cycle into one commit (**squashing**).\n - Leave a comment to request changes — a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review-deciding** → **feedback-building**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": review-deciding\n\n review-deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the cycle start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any 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\": $onSignoff\n\n feedback-collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewRawFile %>` (delete it when done) and\n `<%= it.vars.reviewFeedbackFile %>` (write it). Do NOT change any other\n file — you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Read the two RAW sections and apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A .gtd/REVIEW_FEEDBACK.md\": feedback-building\n \"M .gtd/REVIEW_FEEDBACK.md\": feedback-building\n\n feedback-building:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The edge\n # gate (`enforceFeedbackProgressGate` in src/program.ts) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. `.gtd/`\n holds this workflow's own state — never create, edit, or delete anything\n under `.gtd/` except `<%= it.vars.reviewFeedbackFile %>`, which this\n prompt tells you to address and then delete.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": checking\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\n spec-review:\n actor: agent\n label: Reviewing the package\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `fixing` cap has (see STATES.md / docs/configuration.md).\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. `.gtd/` holds this workflow's own state — never\n create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to write\n when — and only when — you find problems.\n\n The package spec is: <%~ it.read(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\n\n - If it fully satisfies the spec, write NOTHING and finish your turn — a\n clean turn is your approval.\n - If it does not, write `<%= it.vars.specFeedbackFile %>` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"M .gtd/SPEC_FEEDBACK.md\": spec-fix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n spec-fix:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.specFeedbackFile %>`, which this prompt tells you to address\n and then delete.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": adv-checking\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": adv-building\n \"C\": $onDrained\n\n adv-building:\n actor: agent\n label: Building\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/`; in\n particular do NOT delete the package file (the `spec-review` gate reads it\n after you) and never touch `<%= it.vars.nextFile %>` — the `picking` state\n owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\n\n That file describes a set of INDEPENDENT tasks. Implement ALL of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement EXACTLY the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"* **\": adv-checking\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %>\n on:\n \"* **\": picking\n\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n # Advanced package health triple: adv-checking/adv-fixing/adv-escalate -> spec-review.\n - submachine: makeGreen\n name: pkg\n as: { check: adv-checking, fix: adv-fixing, escalate: adv-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n Fix the failure yourself — edit the code and/or\n `.gtd/FEEDBACK.md` — to re-run the check (**adv-checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: start\n as: { check: start-check, blocked: start-blocked }\n with:\n onGreen: planning\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting new work; a red run\n # records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**start-check**). To repair the baseline as\n its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: adv-start\n as: { check: adv-start-check, blocked: adv-start-blocked }\n with:\n onGreen: adv-grilling\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before starting the advanced flow; a red\n # run records .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**adv-start-check**). To repair the baseline\n as its own separate reviewed commit instead, abandon this start and run\n `gtd fix` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review-start\n as: { check: review-start-check, blocked: review-start-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n checkScript: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite before reviewing; a red run records\n # .gtd/FEEDBACK.md (halt), a green run cleans it up (proceed to reviewing).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n Fix the failing tests — edit the code and/or `.gtd/FEEDBACK.md` — to\n re-run the baseline check (**review-start-check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Advanced flow: product Q&A loop (adv-grilling/adv-grilling-answer -> architecting).\n - submachine: qaLoop\n name: grill\n as: { author: adv-grilling, answer: adv-grilling-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: architecting\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.requirementsFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for TWO\n concrete options plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n product plan and move on to technical architecting (**architecting**).\n editDescribe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**adv-grilling**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the product plan\n answerLabel: Awaiting your answers\n\n # Advanced flow: technical Q&A loop (architecting/architecting-answer -> decompose).\n - submachine: qaLoop\n name: arch\n as: { author: architecting, answer: architecting-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.architectureFile %>` and `<%= it.vars.requirementsFile %>`,\n which this prompt tells you to write and delete respectively.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n With no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to accept the\n technical plan and move on to decomposing the work into packages\n (**decompose**).\n editDescribe: >-\n Tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**architecting**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the technical plan\n answerLabel: Awaiting your answers\n\n # Simple flow plan-iteration loop (planning/plan-review -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/adv-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n label: Idle\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `.gtd/TODO.md` — a short sketch — starts the SIMPLE flow (plan, build,\n review).\n - `.gtd/REQUIREMENTS.md` — the product requirements — starts the ADVANCED\n flow (product + technical Q&A, package decomposition, parallel build,\n per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* .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 # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once its\n work is complete.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": checking\n\n # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\n decompose:\n actor: agent\n label: Decomposing into packages\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except the\n package files this prompt tells you to write (under\n `<%= it.vars.packagesDir %>/`), and `<%= it.vars.architectureFile %>`,\n which it tells you to delete.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Order the packages so the\n test suite can pass again after each one lands.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* .gtd/packages/**\": picking\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-check:\n actor: check\n fixEntry: true\n label: Checking the baseline\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite to find what needs fixing; a red run\n # records .gtd/FEEDBACK.md (-> fixing), a green run cleans it up (-> idle).\n set +e\n mkdir -p .gtd\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output \"$feedback\"\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n else\n rm -f .gtd/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # ── Shared tail: squash finale (review loop is the `humanReview` sub-machine) ─\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done.\n `.gtd/` holds this workflow's own state — never create, edit, or delete\n anything under `.gtd/` except `<%= it.vars.commitMsgFile %>`, which this\n prompt tells you to write.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A .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";
|
|
190529
190552
|
//#endregion
|
|
190530
190553
|
//#region src/workflows/templates.ts
|
|
190531
190554
|
/**
|
|
@@ -191805,7 +191828,7 @@ const renderDiff = (files) => {
|
|
|
191805
191828
|
};
|
|
191806
191829
|
//#endregion
|
|
191807
191830
|
//#region src/Git.ts
|
|
191808
|
-
const EMPTY_TREE$
|
|
191831
|
+
const EMPTY_TREE$3 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
191809
191832
|
/**
|
|
191810
191833
|
* Parse `git diff --name-status` output into `{ path, status }` pairs.
|
|
191811
191834
|
* Status codes: A (added), D (deleted), M (modified), R (renamed), C (copied), etc.
|
|
@@ -191919,6 +191942,7 @@ const makeGitImpl = (executor, root) => {
|
|
|
191919
191942
|
})), { concurrency: "unbounded" }));
|
|
191920
191943
|
}),
|
|
191921
191944
|
lastCommitSubject: () => exec("git", "log", "-1", "--pretty=%s").pipe(map$4((s) => s.trim())),
|
|
191945
|
+
lastCommitMessage: () => exec("git", "log", "-1", "--pretty=%B").pipe(map$4((s) => s.trim())),
|
|
191922
191946
|
hasCommits: () => exec("git", "rev-parse", "--verify", "HEAD").pipe(map$4(() => true), catchAll(() => succeed$3(false))),
|
|
191923
191947
|
diffRef: (ref, exclude = []) => gen(function* () {
|
|
191924
191948
|
const nameStatusOut = yield* exec("git", "diff", "--name-status", ref, "HEAD").pipe(catchAll(() => succeed$3("")));
|
|
@@ -191936,7 +191960,7 @@ const makeGitImpl = (executor, root) => {
|
|
|
191936
191960
|
readFileAtRef: (ref, path) => exec("git", "show", `${ref}:${path}`),
|
|
191937
191961
|
commitDiff: (hash, exclude = []) => gen(function* () {
|
|
191938
191962
|
yield* exec("git", "rev-parse", "--verify", hash);
|
|
191939
|
-
const parent = yield* exec("git", "rev-parse", "--verify", `${hash}~1`).pipe(map$4((s) => s.trim()), catchAll(() => succeed$3(EMPTY_TREE$
|
|
191963
|
+
const parent = yield* exec("git", "rev-parse", "--verify", `${hash}~1`).pipe(map$4((s) => s.trim()), catchAll(() => succeed$3(EMPTY_TREE$3)));
|
|
191940
191964
|
const nameStatusOut = yield* exec("git", "diff", "--name-status", parent, hash).pipe(catchAll(() => succeed$3("")));
|
|
191941
191965
|
const allPaths = parseNameStatus(nameStatusOut);
|
|
191942
191966
|
const filtered = applyExcludes(allPaths, exclude);
|
|
@@ -192020,6 +192044,7 @@ const makeGitImpl = (executor, root) => {
|
|
|
192020
192044
|
updateRef: (ref, hash) => exec("git", "update-ref", ref, hash).pipe(asVoid),
|
|
192021
192045
|
deleteRef: (ref) => exec("git", "update-ref", "-d", ref).pipe(asVoid),
|
|
192022
192046
|
mixedResetTo: (ref) => exec("git", "reset", "--mixed", ref).pipe(asVoid),
|
|
192047
|
+
hardResetTo: (ref) => exec("git", "reset", "--hard", ref).pipe(asVoid),
|
|
192023
192048
|
restoreStagedFrom: (source, paths) => paths.length === 0 ? _void : exec("git", "restore", "--staged", `--source=${source}`, "--", ...paths).pipe(catchAll(() => _void)),
|
|
192024
192049
|
addIntentToAdd: () => exec("git", "add", "--intent-to-add", ".").pipe(asVoid)
|
|
192025
192050
|
};
|
|
@@ -192564,6 +192589,76 @@ const renderStateTemplate = (template, context) => eta.renderString(template, co
|
|
|
192564
192589
|
*/
|
|
192565
192590
|
const renderModeCommand = (template, context) => eta.renderString(template, context);
|
|
192566
192591
|
//#endregion
|
|
192592
|
+
//#region src/RetainedHistory.ts
|
|
192593
|
+
/**
|
|
192594
|
+
* Retention of a squashed (or abandoned) process's turn-by-turn history —
|
|
192595
|
+
* Task C of `docs/design/pattern-machine-plan.md`'s follow-up note (see
|
|
192596
|
+
* `.gtd/packages/01-retention-primitives.md`). Squashing collapses a whole
|
|
192597
|
+
* cycle's turn commits into one; this module records the pre-squash tip so a
|
|
192598
|
+
* later `gtd restore` (a separate, not-yet-built command-layer piece of work)
|
|
192599
|
+
* can bring those turns back.
|
|
192600
|
+
*/
|
|
192601
|
+
/**
|
|
192602
|
+
* The retained-history ref lives in git's PER-WORKTREE `refs/worktree/gtd/*`
|
|
192603
|
+
* namespace — the same one `src/ReviewWindow.ts` uses for `REVIEW_HEAD_REF`/
|
|
192604
|
+
* `REVIEW_BASE_REF`, for the same reason: linked worktrees sharing one `.git`
|
|
192605
|
+
* must not clobber each other's retained history (issue #118).
|
|
192606
|
+
*/
|
|
192607
|
+
const HISTORY_REF = "refs/worktree/gtd/history";
|
|
192608
|
+
const HISTORY_TRAILER_PREFIX = "Gtd-History: ";
|
|
192609
|
+
const HISTORY_TRAILER_RE = /^Gtd-History:[ \t]*(\S+)[ \t]*$/m;
|
|
192610
|
+
/**
|
|
192611
|
+
* Append a `Gtd-History: <hash>` trailer (after a blank line) to a commit
|
|
192612
|
+
* `subject` — records the pre-squash tip commit hash so a later `gtd restore`
|
|
192613
|
+
* command can read it back. Mirrors `Edge.ts`'s `withReviewBaseTrailer`
|
|
192614
|
+
* placement: the subject (first line) is untouched.
|
|
192615
|
+
*/
|
|
192616
|
+
const withHistoryTrailer = (subject, hash) => `${subject}\n\n${HISTORY_TRAILER_PREFIX}${hash}`;
|
|
192617
|
+
/**
|
|
192618
|
+
* The `Gtd-History: <hash>` trailer recorded on a squash commit (see
|
|
192619
|
+
* `withHistoryTrailer`), or `undefined` when `message` carries none. Read back
|
|
192620
|
+
* by a later `gtd restore` command to recover the pre-squash tip.
|
|
192621
|
+
*/
|
|
192622
|
+
const parseHistoryTrailer = (message) => HISTORY_TRAILER_RE.exec(message)?.[1];
|
|
192623
|
+
/**
|
|
192624
|
+
* Record the pre-squash tip so a later `gtd restore` can find it. A no-op
|
|
192625
|
+
* (the git call is skipped entirely) when `tipHash === startParentHash`: an
|
|
192626
|
+
* empty process — no turns were ever committed — has no turn chain worth
|
|
192627
|
+
* keeping.
|
|
192628
|
+
*/
|
|
192629
|
+
const retainHistory = (git, tipHash, startParentHash) => tipHash === startParentHash ? _void : git.updateRef(HISTORY_REF, tipHash);
|
|
192630
|
+
/** A thin wrapper over `git.readRefOption(HISTORY_REF)`. */
|
|
192631
|
+
const readRetainedHistory = (git) => git.readRefOption(HISTORY_REF);
|
|
192632
|
+
/**
|
|
192633
|
+
* The safety predicate deciding whether a future `gtd restore` to `tipHash`
|
|
192634
|
+
* would be safe. Accepts exactly two shapes:
|
|
192635
|
+
*
|
|
192636
|
+
* (a) Fresh squash — HEAD IS the squash commit (a brand-new commit distinct
|
|
192637
|
+
* from `tipHash`, the pre-squash tip it collapsed) and its own message
|
|
192638
|
+
* still carries the `Gtd-History:` trailer pointing at that tip
|
|
192639
|
+
* (`parseHistoryTrailer(headMessage) === tipHash`). Hard-resetting from
|
|
192640
|
+
* here discards only that one squash commit, whose tree equals the tip's
|
|
192641
|
+
* — no content loss. If HEAD's trailer names a DIFFERENT hash (or none),
|
|
192642
|
+
* either a newer squash/abandon has since superseded this ref or HEAD
|
|
192643
|
+
* isn't a squash commit at all, so this falls through to (b) rather than
|
|
192644
|
+
* trusting a stale or absent match.
|
|
192645
|
+
* (b) Cleaned abandon / fast-forward — HEAD is an ancestor of the retained
|
|
192646
|
+
* tip (`git.isAncestor(headHash, tipHash)`), i.e. nothing built on top of
|
|
192647
|
+
* the tip would be discarded by resetting back to it.
|
|
192648
|
+
*
|
|
192649
|
+
* Anything else is refused with a reason naming what would be lost.
|
|
192650
|
+
*/
|
|
192651
|
+
const restorability = (git, headHash, headMessage, tipHash) => gen(function* () {
|
|
192652
|
+
if (parseHistoryTrailer(headMessage) === tipHash) return { ok: true };
|
|
192653
|
+
if (yield* git.isAncestor(headHash, tipHash)) return { ok: true };
|
|
192654
|
+
return {
|
|
192655
|
+
ok: false,
|
|
192656
|
+
reason: "HEAD has advanced past the squash — restoring would discard commits built on top of it"
|
|
192657
|
+
};
|
|
192658
|
+
});
|
|
192659
|
+
/** Idempotent: `deleteRef` already tolerates a missing ref. */
|
|
192660
|
+
const clearRetainedHistory = (git) => git.deleteRef(HISTORY_REF);
|
|
192661
|
+
//#endregion
|
|
192567
192662
|
//#region src/Edge.ts
|
|
192568
192663
|
/**
|
|
192569
192664
|
* The v3 Effect edge (see `docs/design/pattern-machine-plan.md`, "Phase 3:
|
|
@@ -192578,7 +192673,7 @@ const renderModeCommand = (template, context) => eta.renderString(template, cont
|
|
|
192578
192673
|
* one commit (or one squash). A caller that wants several transitions issues
|
|
192579
192674
|
* several invocations.
|
|
192580
192675
|
*/
|
|
192581
|
-
const EMPTY_TREE$
|
|
192676
|
+
const EMPTY_TREE$2 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
192582
192677
|
const subjectOf$1 = (message) => (message.split("\n")[0] ?? "").trim();
|
|
192583
192678
|
const COST_TRAILER_PREFIX = "Gtd-Cost: ";
|
|
192584
192679
|
const COST_TRAILER_RE = /^Gtd-Cost:[ \t]*([0-9]+(?:\.[0-9]+)?)(?:[ \t]+(.+?))?[ \t]*$/gm;
|
|
@@ -192674,8 +192769,8 @@ const pendingChanges = (git) => git.changedPaths().pipe(map$4((entries) => entri
|
|
|
192674
192769
|
const computeProcessRun = (git, def) => gen(function* () {
|
|
192675
192770
|
if (!(yield* git.hasCommits())) return {
|
|
192676
192771
|
startHash: "",
|
|
192677
|
-
startParentHash: EMPTY_TREE$
|
|
192678
|
-
diffBase: EMPTY_TREE$
|
|
192772
|
+
startParentHash: EMPTY_TREE$2,
|
|
192773
|
+
diffBase: EMPTY_TREE$2,
|
|
192679
192774
|
trace: [],
|
|
192680
192775
|
costEntries: []
|
|
192681
192776
|
};
|
|
@@ -192691,7 +192786,7 @@ const computeProcessRun = (git, def) => gen(function* () {
|
|
|
192691
192786
|
const processCommits = history.slice(startIdx);
|
|
192692
192787
|
const trace = processCommits.map((h) => parseStateSubject(subjectOf$1(h.message)).state);
|
|
192693
192788
|
const costEntries = parseCostTrailers(processCommits.map((h) => h.message));
|
|
192694
|
-
const startParentHash = i >= 0 ? history[i].hash : EMPTY_TREE$
|
|
192789
|
+
const startParentHash = i >= 0 ? history[i].hash : EMPTY_TREE$2;
|
|
192695
192790
|
return {
|
|
192696
192791
|
startHash: startIdx < history.length ? history[startIdx].hash : history[history.length - 1].hash,
|
|
192697
192792
|
startParentHash,
|
|
@@ -192814,6 +192909,18 @@ const renderMemory = (stateDef, context) => try_({
|
|
|
192814
192909
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
192815
192910
|
});
|
|
192816
192911
|
/**
|
|
192912
|
+
* Render a state's declared `label:` (if any) through the SAME template
|
|
192913
|
+
* context as its content/`model` — see `renderModel`'s doc comment; a plain
|
|
192914
|
+
* label (e.g. `"planning"`) passes through unchanged, while
|
|
192915
|
+
* `label: "<%= it.vars.labelName %>"` resolves against the merged `it.vars`.
|
|
192916
|
+
* The render-failure semantics are identical (propagates as a thrown/rejected
|
|
192917
|
+
* error, same call site as `gtd next`/`gtd status`).
|
|
192918
|
+
*/
|
|
192919
|
+
const renderLabel = (stateDef, context) => try_({
|
|
192920
|
+
try: () => stateDef.label !== void 0 ? renderStateTemplate(stateDef.label, context) : void 0,
|
|
192921
|
+
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
192922
|
+
});
|
|
192923
|
+
/**
|
|
192817
192924
|
* Render a state's declared `file:` steering-file template (if any) through
|
|
192818
192925
|
* the SAME template context as its content/`model` — see `renderModel`'s doc
|
|
192819
192926
|
* comment; the render-failure semantics are identical (propagates as a
|
|
@@ -192823,7 +192930,8 @@ const renderFile = (stateDef, context) => try_({
|
|
|
192823
192930
|
try: () => stateDef.file !== void 0 ? renderStateTemplate(stateDef.file, context) : void 0,
|
|
192824
192931
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
192825
192932
|
});
|
|
192826
|
-
|
|
192933
|
+
const omitUndefined = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
192934
|
+
/** Render the resolved rest's declared content (script/prompt/message — never `commit`, since `resolveRest` never rests at a commit state) plus its `model:`/`memory:`/`label:`/`file:` hints, if declared (see `renderModel`/`renderMemory`/`renderLabel`/`renderFile`). `mode:` is a closed literal, never Eta-rendered — passed through verbatim. */
|
|
192827
192935
|
const renderRest = (rest, context) => gen(function* () {
|
|
192828
192936
|
const kind = contentKindOf(rest.stateDef);
|
|
192829
192937
|
if (kind === void 0) return yield* fail$2(/* @__PURE__ */ new Error(`state "${rest.state}" declares no content — invalid definition`));
|
|
@@ -192832,18 +192940,19 @@ const renderRest = (rest, context) => gen(function* () {
|
|
|
192832
192940
|
try: () => renderStateTemplate(template, context),
|
|
192833
192941
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
192834
192942
|
});
|
|
192835
|
-
const
|
|
192836
|
-
|
|
192837
|
-
|
|
192943
|
+
const hints = {
|
|
192944
|
+
model: yield* renderModel(rest.stateDef, context),
|
|
192945
|
+
memory: yield* renderMemory(rest.stateDef, context),
|
|
192946
|
+
label: yield* renderLabel(rest.stateDef, context),
|
|
192947
|
+
file: yield* renderFile(rest.stateDef, context),
|
|
192948
|
+
mode: rest.stateDef.mode
|
|
192949
|
+
};
|
|
192838
192950
|
return {
|
|
192839
192951
|
state: rest.state,
|
|
192840
192952
|
actor: rest.actor,
|
|
192841
192953
|
kind,
|
|
192842
192954
|
content,
|
|
192843
|
-
...
|
|
192844
|
-
...memory !== void 0 ? { memory } : {},
|
|
192845
|
-
...file !== void 0 ? { file } : {},
|
|
192846
|
-
...rest.stateDef.mode !== void 0 ? { mode: rest.stateDef.mode } : {},
|
|
192955
|
+
...omitUndefined(hints),
|
|
192847
192956
|
edges: toTemplateEdges(rest.stateDef.on)
|
|
192848
192957
|
};
|
|
192849
192958
|
});
|
|
@@ -192853,13 +192962,15 @@ const renderRest = (rest, context) => gen(function* () {
|
|
|
192853
192962
|
* `Gtd-Cost: <cost>[ <model>]` trailer (the invocation's `--cost`/`--model`,
|
|
192854
192963
|
* persisted in the git log); a `"squash"` decision renders the commit-state
|
|
192855
192964
|
* template against the PENDING tree — a render failure REFUSES the step,
|
|
192856
|
-
* touching nothing — then
|
|
192857
|
-
*
|
|
192858
|
-
*
|
|
192859
|
-
*
|
|
192860
|
-
*
|
|
192861
|
-
*
|
|
192862
|
-
*
|
|
192965
|
+
* touching nothing — then records the pre-squash tip on the retained-history
|
|
192966
|
+
* ref (`retainHistory`, a no-op for an empty process) before soft-resetting to
|
|
192967
|
+
* the process's start parent, writes ONE commit with the rendered message
|
|
192968
|
+
* plus a `Gtd-History: <hash>` trailer pointing at that tip (via
|
|
192969
|
+
* `withHistoryTrailer`/`commitAsIs`, so the still-uncommitted template file is
|
|
192970
|
+
* excluded), and discards everything left pending (the template file
|
|
192971
|
+
* included). The whole-process total and per-model breakdown reach the
|
|
192972
|
+
* message through `it.processCost`/`it.processCostByModel` in the rendered
|
|
192973
|
+
* template. A `"noop"` performs no IO.
|
|
192863
192974
|
*/
|
|
192864
192975
|
const executeDecision = (git, run, decision, context, cost, model) => gen(function* () {
|
|
192865
192976
|
switch (decision.kind) {
|
|
@@ -192874,8 +192985,10 @@ const executeDecision = (git, run, decision, context, cost, model) => gen(functi
|
|
|
192874
192985
|
try: () => renderStateTemplate(decision.template, context),
|
|
192875
192986
|
catch: (e) => /* @__PURE__ */ new Error(`gtd: rendering the "${decision.state}" commit template failed — nothing was committed: ${e instanceof Error ? e.message : String(e)}`)
|
|
192876
192987
|
});
|
|
192988
|
+
const tip = yield* git.resolveRef("HEAD");
|
|
192989
|
+
yield* retainHistory(git, tip, run.startParentHash);
|
|
192877
192990
|
yield* git.softResetTo(run.startParentHash);
|
|
192878
|
-
yield* git.commitAsIs(message);
|
|
192991
|
+
yield* git.commitAsIs(withHistoryTrailer(message, tip));
|
|
192879
192992
|
yield* git.discardPending();
|
|
192880
192993
|
return {
|
|
192881
192994
|
kind: "squash",
|
|
@@ -192908,25 +193021,45 @@ const executeDecision = (git, run, decision, context, cost, model) => gen(functi
|
|
|
192908
193021
|
* every state subcommand:
|
|
192909
193022
|
*
|
|
192910
193023
|
* - `closeReviewWindow` runs BEFORE anything reads or mutates state, keyed
|
|
192911
|
-
* solely on `
|
|
192912
|
-
*
|
|
192913
|
-
*
|
|
192914
|
-
*
|
|
192915
|
-
* sees the window.
|
|
193024
|
+
* solely on `REVIEW_HEAD_REF` existing: `git reset --mixed <that ref>`
|
|
193025
|
+
* restores HEAD/index exactly, leaving only the reviewer's own edits dirty
|
|
193026
|
+
* (captured by the resting state's own `on` patterns like any other pending
|
|
193027
|
+
* change). The pure machine therefore never sees the window.
|
|
192916
193028
|
* - `openReviewWindow` runs AFTER the subcommand finishes and self-guards on
|
|
192917
193029
|
* the resolved rest declaring `reviewWindow: true`: it saves HEAD to
|
|
192918
|
-
* `
|
|
192919
|
-
*
|
|
192920
|
-
*
|
|
192921
|
-
*
|
|
193030
|
+
* `REVIEW_HEAD_REF` (the base to `REVIEW_BASE_REF`), then `git reset --mixed
|
|
193031
|
+
* <base>`. It re-arms after read-only commands (`gtd next` / `gtd status`)
|
|
193032
|
+
* and refused invocations too, so the editor's diff view stays consistent no
|
|
193033
|
+
* matter which command the loop last ran.
|
|
192922
193034
|
*
|
|
192923
193035
|
* Every open/close step is idempotent under re-entry, so a crash at any point
|
|
192924
193036
|
* is recovered by the next invocation's close (the saved ref also keeps the
|
|
192925
193037
|
* real head GC-reachable for the window's whole lifetime).
|
|
192926
193038
|
*/
|
|
192927
|
-
|
|
192928
|
-
|
|
192929
|
-
|
|
193039
|
+
/**
|
|
193040
|
+
* The window's two state refs live in git's PER-WORKTREE `refs/worktree/*`
|
|
193041
|
+
* namespace, so linked worktrees sharing one `.git` (`git worktree add`) each
|
|
193042
|
+
* get their OWN window: a review resting in one worktree is invisible to — and
|
|
193043
|
+
* un-clobberable by — every other one, and git drops the refs with the
|
|
193044
|
+
* worktree.
|
|
193045
|
+
*
|
|
193046
|
+
* gtd ≤ 7.1 kept them in the SHARED `refs/gtd/*` namespace (the legacy names
|
|
193047
|
+
* below), which made a second worktree's very first gtd invocation close the
|
|
193048
|
+
* FIRST worktree's window: its `git reset --mixed` moved the second worktree's
|
|
193049
|
+
* branch onto the other worktree's saved head, and every later invocation
|
|
193050
|
+
* refused with "a process is already underway" (issue #118).
|
|
193051
|
+
*/
|
|
193052
|
+
const REVIEW_HEAD_REF = "refs/worktree/gtd/review-head";
|
|
193053
|
+
const REVIEW_BASE_REF = "refs/worktree/gtd/review-base";
|
|
193054
|
+
/**
|
|
193055
|
+
* The pre-7.2 SHARED refs. gtd never writes them any more; `closeReviewWindow`
|
|
193056
|
+
* only reads them to finish a window an older gtd left open across the upgrade
|
|
193057
|
+
* — and only when HEAD is still contained in the saved head, since a shared ref
|
|
193058
|
+
* may belong to a sibling worktree (see `openWindowRefs`).
|
|
193059
|
+
*/
|
|
193060
|
+
const LEGACY_REVIEW_HEAD_REF = "refs/gtd/review-head";
|
|
193061
|
+
const LEGACY_REVIEW_BASE_REF = "refs/gtd/review-base";
|
|
193062
|
+
const EMPTY_TREE$1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
192930
193063
|
const subjectOf = (message) => (message.split("\n")[0] ?? "").trim();
|
|
192931
193064
|
/**
|
|
192932
193065
|
* The diff base while the window is open: the hash of the most-recent
|
|
@@ -192945,28 +193078,63 @@ const reviewBaseHash = (git, def, run) => gen(function* () {
|
|
|
192945
193078
|
}
|
|
192946
193079
|
return base;
|
|
192947
193080
|
});
|
|
193081
|
+
/** The manual-recovery incantation every close refusal ends with. */
|
|
193082
|
+
const manualRecovery = (refs) => `\`git reset --mixed ${refs.headRef} && git update-ref -d ${refs.headRef} && git update-ref -d ${refs.baseRef}\``;
|
|
193083
|
+
/**
|
|
193084
|
+
* Which ref pair (if any) records an open window: this worktree's own
|
|
193085
|
+
* `refs/worktree/gtd/*` pair first, falling back to the pre-7.2 shared
|
|
193086
|
+
* `refs/gtd/*` pair so an upgrade mid-window still closes cleanly.
|
|
193087
|
+
*/
|
|
193088
|
+
const openWindowRefs = (git) => gen(function* () {
|
|
193089
|
+
const scoped = yield* git.readRefOption(REVIEW_HEAD_REF);
|
|
193090
|
+
if (isSome(scoped)) return some({
|
|
193091
|
+
headRef: REVIEW_HEAD_REF,
|
|
193092
|
+
baseRef: REVIEW_BASE_REF,
|
|
193093
|
+
headHash: scoped.value,
|
|
193094
|
+
legacy: false
|
|
193095
|
+
});
|
|
193096
|
+
const legacy = yield* git.readRefOption(LEGACY_REVIEW_HEAD_REF);
|
|
193097
|
+
if (isNone(legacy)) return none$4();
|
|
193098
|
+
return some({
|
|
193099
|
+
headRef: LEGACY_REVIEW_HEAD_REF,
|
|
193100
|
+
baseRef: LEGACY_REVIEW_BASE_REF,
|
|
193101
|
+
headHash: legacy.value,
|
|
193102
|
+
legacy: true
|
|
193103
|
+
});
|
|
193104
|
+
});
|
|
192948
193105
|
/**
|
|
192949
193106
|
* Restore the real head if a review checkout window is open; no-op otherwise.
|
|
192950
|
-
* Keyed on
|
|
192951
|
-
* state, so it always runs first and the machine never sees
|
|
193107
|
+
* Keyed on the saved-head ref's existence (see `openWindowRefs`), NOT on any
|
|
193108
|
+
* config or machine state, so it always runs first and the machine never sees
|
|
193109
|
+
* the window.
|
|
192952
193110
|
*
|
|
192953
193111
|
* Fails loudly — refs left in place — when HEAD is no longer on the reviewed
|
|
192954
193112
|
* branch (the base is not an ancestor of HEAD, e.g. after a branch switch): a
|
|
192955
193113
|
* mixed reset there would rewrite the wrong branch's tip. Manual commits on
|
|
192956
193114
|
* top of the base pass the guard: the reset keeps their content in the working
|
|
192957
193115
|
* tree, where the next turn's capture picks it up as review feedback.
|
|
193116
|
+
*
|
|
193117
|
+
* A window found under the LEGACY shared refs gets one extra guard: a shared
|
|
193118
|
+
* ref may have been written by a sibling worktree, so the close only proceeds
|
|
193119
|
+
* when HEAD is contained in the saved head — the shape a window this worktree
|
|
193120
|
+
* opened itself has. Anything else refuses with the manual recovery rather
|
|
193121
|
+
* than resetting this branch onto another worktree's work (issue #118).
|
|
192958
193122
|
*/
|
|
192959
193123
|
const closeReviewWindow = gen(function* () {
|
|
192960
193124
|
const git = yield* GitService;
|
|
192961
|
-
const
|
|
192962
|
-
if (isNone(
|
|
192963
|
-
const
|
|
192964
|
-
|
|
192965
|
-
|
|
192966
|
-
|
|
192967
|
-
|
|
192968
|
-
|
|
192969
|
-
|
|
193125
|
+
const open = yield* openWindowRefs(git);
|
|
193126
|
+
if (isNone(open)) return { closed: false };
|
|
193127
|
+
const refs = open.value;
|
|
193128
|
+
const base = yield* git.readRefOption(refs.baseRef);
|
|
193129
|
+
if (isSome(base) && base.value !== EMPTY_TREE$1) {
|
|
193130
|
+
if (!(yield* git.isAncestor(base.value, "HEAD"))) return yield* fail$2(/* @__PURE__ */ new Error(`a gtd review checkout window is open but HEAD has moved off the reviewed branch — return to it, or restore manually with ${manualRecovery(refs)}`));
|
|
193131
|
+
}
|
|
193132
|
+
if (refs.legacy) {
|
|
193133
|
+
if (!(yield* git.isAncestor("HEAD", refs.headHash))) return yield* fail$2(/* @__PURE__ */ new Error(`a review checkout window is recorded under the shared ${LEGACY_REVIEW_HEAD_REF} (written by gtd 7.1 or older) but it does not belong to this worktree — HEAD is not contained in it. gtd now scopes the window per worktree (${REVIEW_HEAD_REF}); in the worktree that owns it, run ${manualRecovery(refs)}`));
|
|
193134
|
+
}
|
|
193135
|
+
yield* git.mixedResetTo(refs.headRef);
|
|
193136
|
+
yield* git.deleteRef(refs.headRef);
|
|
193137
|
+
yield* git.deleteRef(refs.baseRef);
|
|
192970
193138
|
return { closed: true };
|
|
192971
193139
|
});
|
|
192972
193140
|
/**
|
|
@@ -192996,7 +193164,7 @@ const openReviewWindow = gen(function* () {
|
|
|
192996
193164
|
const run = yield* computeProcessRun(git, def);
|
|
192997
193165
|
const base = (yield* reviewBaseHash(git, def, run)) ?? run.diffBase;
|
|
192998
193166
|
const headHash = yield* git.resolveRef("HEAD");
|
|
192999
|
-
if (base === EMPTY_TREE || base === headHash) return { opened: false };
|
|
193167
|
+
if (base === EMPTY_TREE$1 || base === headHash) return { opened: false };
|
|
193000
193168
|
yield* git.updateRef(REVIEW_BASE_REF, base);
|
|
193001
193169
|
yield* git.updateRef(REVIEW_HEAD_REF, headHash);
|
|
193002
193170
|
yield* git.mixedResetTo(base);
|
|
@@ -205112,6 +205280,17 @@ Commands:
|
|
|
205112
205280
|
state (fixEntry: true) that goes straight into repairing the
|
|
205113
205281
|
current failing tests. Requires a clean tree resting at the
|
|
205114
205282
|
workflow's initial state
|
|
205283
|
+
abandon End the process currently underway without completing it:
|
|
205284
|
+
close any open review checkout window, then rewind HEAD to
|
|
205285
|
+
the commit the process started from, keeping everything it
|
|
205286
|
+
produced as uncommitted changes. A no-op when no process is
|
|
205287
|
+
underway
|
|
205288
|
+
restore Hard-reset HEAD back to the pre-squash tip retained by the
|
|
205289
|
+
last squash/abandon (refs/worktree/gtd/history), undoing a
|
|
205290
|
+
squash or bringing back an abandoned process's turns.
|
|
205291
|
+
Refuses on a dirty working tree, when there is no retained
|
|
205292
|
+
history, or when HEAD has advanced past the squash with
|
|
205293
|
+
commits that would be lost
|
|
205115
205294
|
next Print the resolved rest's rendered script/prompt/message
|
|
205116
205295
|
(no mutation)
|
|
205117
205296
|
status Print the resolved rest's state/actor and which declared
|
|
@@ -205375,7 +205554,7 @@ const runReviewCommand = (argv, json, write) => gen(function* () {
|
|
|
205375
205554
|
const commitish = args[0];
|
|
205376
205555
|
const git = yield* GitService;
|
|
205377
205556
|
const rest = yield* resolveRest();
|
|
205378
|
-
if (rest.state !== initialStateOf(rest.def)) return yield* fail$2(/* @__PURE__ */ new Error(`gtd review: a process is already underway (resting at "${rest.state}") — finish or abandon
|
|
205557
|
+
if (rest.state !== initialStateOf(rest.def)) return yield* fail$2(/* @__PURE__ */ new Error(`gtd review: a process is already underway (resting at "${rest.state}") — finish it, or run \`gtd abandon\`, before starting a review`));
|
|
205379
205558
|
if ((yield* pendingChanges(git)).length > 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd review: the working tree must be clean before starting a review"));
|
|
205380
205559
|
const entryState = reviewEntryStateOf(rest.def);
|
|
205381
205560
|
if (entryState === void 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd review: the active workflow declares no review entry state"));
|
|
@@ -205412,7 +205591,7 @@ const runFixCommand = (argv, json, write) => gen(function* () {
|
|
|
205412
205591
|
yield* rejectExtraArgs("fix", argv);
|
|
205413
205592
|
const git = yield* GitService;
|
|
205414
205593
|
const rest = yield* resolveRest();
|
|
205415
|
-
if (rest.state !== initialStateOf(rest.def)) return yield* fail$2(/* @__PURE__ */ new Error(`gtd fix: a process is already underway (resting at "${rest.state}") — finish or abandon
|
|
205594
|
+
if (rest.state !== initialStateOf(rest.def)) return yield* fail$2(/* @__PURE__ */ new Error(`gtd fix: a process is already underway (resting at "${rest.state}") — finish it, or run \`gtd abandon\`, before starting a fix`));
|
|
205416
205595
|
if ((yield* pendingChanges(git)).length > 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd fix: the working tree must be clean before starting a fix"));
|
|
205417
205596
|
const entryState = fixEntryStateOf(rest.def);
|
|
205418
205597
|
if (entryState === void 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd fix: the active workflow declares no fix entry state"));
|
|
@@ -205424,6 +205603,89 @@ const runFixCommand = (argv, json, write) => gen(function* () {
|
|
|
205424
205603
|
}) + "\n");
|
|
205425
205604
|
else write(`committed: ${subject}\n`);
|
|
205426
205605
|
});
|
|
205606
|
+
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
205607
|
+
/**
|
|
205608
|
+
* `gtd abandon`: end the process currently underway WITHOUT completing it,
|
|
205609
|
+
* returning the machine to the workflow's initial state — the recovery path out
|
|
205610
|
+
* of a process nobody is going to finish (the refusals of `gtd review`/`gtd fix`
|
|
205611
|
+
* name it: "finish or abandon it before starting a review").
|
|
205612
|
+
*
|
|
205613
|
+
* NOTHING is discarded. The shared bracket in `makeProgram` has already closed
|
|
205614
|
+
* any open review checkout window (so HEAD is the real head), and abandon then
|
|
205615
|
+
* `git reset --mixed`es HEAD to the commit the process started from
|
|
205616
|
+
* (`computeProcessRun`'s `startParentHash` — the same boundary a squash resets
|
|
205617
|
+
* to). Every turn commit the process wrote is dropped, and everything they
|
|
205618
|
+
* carried — the code, the `.gtd/` steering files — stays in the working tree as
|
|
205619
|
+
* uncommitted changes for the human to keep, re-commit, or discard with
|
|
205620
|
+
* ordinary git.
|
|
205621
|
+
*
|
|
205622
|
+
* Idempotent: resting at the initial state (a plain non-gtd branch, or a
|
|
205623
|
+
* just-squashed cycle) is a no-op SUCCESS, not a refusal — a recovery command
|
|
205624
|
+
* that fails when there is nothing to recover is a worse tool. The one refusal
|
|
205625
|
+
* is a process whose first commit is the repository's own root commit: there is
|
|
205626
|
+
* no earlier commit to rewind to.
|
|
205627
|
+
*/
|
|
205628
|
+
const runAbandonCommand = (argv, json, write) => gen(function* () {
|
|
205629
|
+
yield* rejectExtraArgs("abandon", argv);
|
|
205630
|
+
const git = yield* GitService;
|
|
205631
|
+
const rest = yield* resolveRest();
|
|
205632
|
+
const initial = initialStateOf(rest.def);
|
|
205633
|
+
if (rest.state === initial) {
|
|
205634
|
+
if (json) write(JSON.stringify({
|
|
205635
|
+
state: initial,
|
|
205636
|
+
abandoned: false
|
|
205637
|
+
}) + "\n");
|
|
205638
|
+
else write(`no gtd process is underway (resting at "${initial}") — nothing to abandon\n`);
|
|
205639
|
+
return;
|
|
205640
|
+
}
|
|
205641
|
+
const run = yield* computeProcessRun(git, rest.def);
|
|
205642
|
+
if (run.startParentHash === EMPTY_TREE) return yield* fail$2(/* @__PURE__ */ new Error(`gtd abandon: the process underway (resting at "${rest.state}") starts at the repository's first commit — there is no earlier commit to rewind to`));
|
|
205643
|
+
yield* retainHistory(git, yield* git.resolveRef("HEAD"), run.startParentHash);
|
|
205644
|
+
yield* git.mixedResetTo(run.startParentHash);
|
|
205645
|
+
const subject = yield* git.lastCommitSubject();
|
|
205646
|
+
if (json) write(JSON.stringify({
|
|
205647
|
+
state: initial,
|
|
205648
|
+
abandoned: true,
|
|
205649
|
+
from: rest.state,
|
|
205650
|
+
head: run.startParentHash
|
|
205651
|
+
}) + "\n");
|
|
205652
|
+
else write(`abandoned the process resting at "${rest.state}" — HEAD is back at ${run.startParentHash.slice(0, 7)} ("${subject}"), resting at "${initial}".\nEverything the process produced is kept as uncommitted changes (\`git status\`); discard them with \`git checkout -- . && git clean -fd .gtd\` for a clean tree.\n`);
|
|
205653
|
+
});
|
|
205654
|
+
/**
|
|
205655
|
+
* `gtd restore`: hard-reset HEAD back to the pre-squash tip a squash or
|
|
205656
|
+
* `gtd abandon` retained (`RetainedHistory.ts`'s `HISTORY_REF`), undoing
|
|
205657
|
+
* either. Unlike `gtd abandon` (which rewinds a process still underway),
|
|
205658
|
+
* restore reaches BACK past a completed squash — or re-applies an abandon's
|
|
205659
|
+
* own retained tip — to bring the turn-by-turn history back.
|
|
205660
|
+
*
|
|
205661
|
+
* Guarded by `restorability` so it never discards work it didn't create:
|
|
205662
|
+
* refuses on a dirty working tree, when there is no retained history, and
|
|
205663
|
+
* when HEAD has advanced past the retained tip with commits that would be
|
|
205664
|
+
* lost by resetting.
|
|
205665
|
+
*/
|
|
205666
|
+
const runRestoreCommand = (argv, json, write) => gen(function* () {
|
|
205667
|
+
yield* rejectExtraArgs("restore", argv);
|
|
205668
|
+
const git = yield* GitService;
|
|
205669
|
+
if ((yield* pendingChanges(git)).length > 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd restore: refuses on a dirty working tree — commit, stash, or discard your changes first."));
|
|
205670
|
+
const retained = yield* readRetainedHistory(git);
|
|
205671
|
+
if (isNone(retained)) return yield* fail$2(/* @__PURE__ */ new Error("gtd restore: no retained history to restore."));
|
|
205672
|
+
const tip = retained.value;
|
|
205673
|
+
const before = yield* resolveRest();
|
|
205674
|
+
const headHash = yield* git.resolveRef("HEAD");
|
|
205675
|
+
const check = yield* restorability(git, headHash, yield* git.lastCommitMessage(), tip);
|
|
205676
|
+
if (!check.ok) return yield* fail$2(/* @__PURE__ */ new Error(`gtd restore: ${check.reason} — HEAD ${headHash.slice(0, 7)} is ahead of the retained tip ${tip.slice(0, 7)}.`));
|
|
205677
|
+
yield* git.hardResetTo(tip);
|
|
205678
|
+
yield* clearRetainedHistory(git);
|
|
205679
|
+
const after = yield* resolveRest();
|
|
205680
|
+
const subject = yield* git.lastCommitSubject();
|
|
205681
|
+
if (json) write(JSON.stringify({
|
|
205682
|
+
state: after.state,
|
|
205683
|
+
restored: true,
|
|
205684
|
+
to: tip,
|
|
205685
|
+
from: before.state
|
|
205686
|
+
}) + "\n");
|
|
205687
|
+
else write(`restored the retained history — HEAD is back at ${tip.slice(0, 7)} ("${subject}"), resting at "${after.state}". Resume with the loop, or \`git reset\` to any earlier turn to restart from there.\n`);
|
|
205688
|
+
});
|
|
205427
205689
|
/**
|
|
205428
205690
|
* The self-validation instruction gtd APPENDS to a `prompt` rest that declares
|
|
205429
205691
|
* both `file:` and `mode:` — i.e. a state whose actor hands over a steering
|
|
@@ -205448,6 +205710,7 @@ const nextJsonOutput = (rendered) => JSON.stringify({
|
|
|
205448
205710
|
content: rendered.content,
|
|
205449
205711
|
...rendered.model !== void 0 ? { model: rendered.model } : {},
|
|
205450
205712
|
...rendered.memory !== void 0 ? { memory: rendered.memory } : {},
|
|
205713
|
+
...rendered.label !== void 0 ? { label: rendered.label } : {},
|
|
205451
205714
|
...rendered.file !== void 0 ? { file: rendered.file } : {},
|
|
205452
205715
|
...rendered.mode !== void 0 ? { mode: rendered.mode } : {},
|
|
205453
205716
|
...rendered.edges.length > 0 ? { edges: rendered.edges } : {}
|
|
@@ -205727,8 +205990,8 @@ const costStatusLines = (cost, byModel) => {
|
|
|
205727
205990
|
if (breakdownIsInformative(byModel)) for (const m of byModel) lines.push(` ${m.model}: ${m.cost}`);
|
|
205728
205991
|
return lines;
|
|
205729
205992
|
};
|
|
205730
|
-
/** `gtd status --json`'s emission — `{state, actor, changes, model?, memory?, file?, mode?, cost?, costByModel?, edges?}`. */
|
|
205731
|
-
const writeStatusJson = (write, rest, statusChanges, model, memory, file, cost, costByModel) => {
|
|
205993
|
+
/** `gtd status --json`'s emission — `{state, actor, changes, model?, memory?, label?, file?, mode?, cost?, costByModel?, edges?}`. */
|
|
205994
|
+
const writeStatusJson = (write, rest, statusChanges, model, memory, label, file, cost, costByModel) => {
|
|
205732
205995
|
const edges = toTemplateEdges(rest.stateDef.on);
|
|
205733
205996
|
write(JSON.stringify({
|
|
205734
205997
|
state: rest.state,
|
|
@@ -205736,6 +205999,7 @@ const writeStatusJson = (write, rest, statusChanges, model, memory, file, cost,
|
|
|
205736
205999
|
changes: statusChanges,
|
|
205737
206000
|
...model !== void 0 ? { model } : {},
|
|
205738
206001
|
...memory !== void 0 ? { memory } : {},
|
|
206002
|
+
...label !== void 0 ? { label } : {},
|
|
205739
206003
|
...file !== void 0 ? { file } : {},
|
|
205740
206004
|
...rest.stateDef.mode !== void 0 ? { mode: rest.stateDef.mode } : {},
|
|
205741
206005
|
...cost > 0 ? { cost } : {},
|
|
@@ -205743,9 +206007,10 @@ const writeStatusJson = (write, rest, statusChanges, model, memory, file, cost,
|
|
|
205743
206007
|
...edges.length > 0 ? { edges } : {}
|
|
205744
206008
|
}) + "\n");
|
|
205745
206009
|
};
|
|
205746
|
-
/** `gtd status`'s plain-text emission — `State:`/`Awaits:`/`Model:`/`Memory:`/`File:`/`Mode:`/`Cost:`/`Pending:` lines. */
|
|
205747
|
-
const writeStatusPlain = (write, rest, statusChanges, model, memory, file, cost, costByModel) => {
|
|
206010
|
+
/** `gtd status`'s plain-text emission — `State:`/`Awaits:`/`Label:`/`Model:`/`Memory:`/`File:`/`Mode:`/`Cost:`/`Pending:` lines. */
|
|
206011
|
+
const writeStatusPlain = (write, rest, statusChanges, model, memory, label, file, cost, costByModel) => {
|
|
205748
206012
|
const lines = [`State: ${rest.state}`, `Awaits: ${rest.actor}`];
|
|
206013
|
+
if (label !== void 0) lines.push(`Label: ${label}`);
|
|
205749
206014
|
if (model !== void 0) lines.push(`Model: ${model}`);
|
|
205750
206015
|
if (memory !== void 0) lines.push(`Memory: ${memory}`);
|
|
205751
206016
|
if (file !== void 0) lines.push(`File: ${file}`);
|
|
@@ -205766,10 +206031,11 @@ const runStatusCommand = (argv, json, write) => gen(function* () {
|
|
|
205766
206031
|
const changes = yield* pendingChanges(git);
|
|
205767
206032
|
const model = yield* renderModel(rest.stateDef, context);
|
|
205768
206033
|
const memory = yield* renderMemory(rest.stateDef, context);
|
|
206034
|
+
const label = yield* renderLabel(rest.stateDef, context);
|
|
205769
206035
|
const file = yield* renderFile(rest.stateDef, context);
|
|
205770
206036
|
const statusChanges = computeStatusChanges(rest.stateDef.on ?? [], changes);
|
|
205771
|
-
if (json) writeStatusJson(write, rest, statusChanges, model, memory, file, context.processCost, context.processCostByModel);
|
|
205772
|
-
else writeStatusPlain(write, rest, statusChanges, model, memory, file, context.processCost, context.processCostByModel);
|
|
206037
|
+
if (json) writeStatusJson(write, rest, statusChanges, model, memory, label, file, context.processCost, context.processCostByModel);
|
|
206038
|
+
else writeStatusPlain(write, rest, statusChanges, model, memory, label, file, context.processCost, context.processCostByModel);
|
|
205773
206039
|
});
|
|
205774
206040
|
/** Parse a `--port` value: an integer 0–65535, or `undefined` if invalid/absent. */
|
|
205775
206041
|
const parsePort = (value) => {
|
|
@@ -205852,6 +206118,8 @@ const KNOWN_SUBCOMMANDS = [
|
|
|
205852
206118
|
"step",
|
|
205853
206119
|
"review",
|
|
205854
206120
|
"fix",
|
|
206121
|
+
"abandon",
|
|
206122
|
+
"restore",
|
|
205855
206123
|
"next",
|
|
205856
206124
|
"status",
|
|
205857
206125
|
"validate"
|
|
@@ -205923,6 +206191,8 @@ const dispatchKnownSubcommand = (sub, argv, json, write, cost, model) => {
|
|
|
205923
206191
|
case "step": return runStepCommand(argv, json, write, cost, model);
|
|
205924
206192
|
case "review": return runReviewCommand(argv, json, write);
|
|
205925
206193
|
case "fix": return runFixCommand(argv, json, write);
|
|
206194
|
+
case "abandon": return runAbandonCommand(argv, json, write);
|
|
206195
|
+
case "restore": return runRestoreCommand(argv, json, write);
|
|
205926
206196
|
case "next": return runNextCommand(json, write);
|
|
205927
206197
|
case "status": return runStatusCommand(argv, json, write);
|
|
205928
206198
|
case "validate": return runValidateCommand(argv, json, write);
|