@pmelab/gtd 4.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,29 +62,34 @@ Four commands drive it:
62
62
  transition the pending changes match.
63
63
  - **`gtd next [--json]`** — print whichever actor is awaited and what they
64
64
  should do, without mutating anything.
65
- - **`gtd run`** — execute the awaited script (the only place gtd itself spawns a
66
- subprocess), then step its actor.
67
65
  - **`gtd status`** — a dry-run report of the resolved state and which pattern
68
66
  each pending change matches.
67
+ - **`gtd review <commitish>`** — start a brand new review process reviewing
68
+ `<commitish>..HEAD` (e.g. a colleague's PR branch), reusing the workflow's
69
+ existing review/feedback machinery over that diff.
69
70
 
70
71
  The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
71
- `"message"` means it's a human's move (stop and hand off); `"script"` means
72
- `gtd run` it; `"prompt"` means feed `content` to your agent, then run
73
- `gtd step <actor>` once it's done. See [STATES.md](STATES.md) for the model and
74
- [Driving the loop](docs/loop.md) for the full protocol.
72
+ `"message"` means it's a human's move (stop and hand off); `"script"` means the
73
+ driver runs `content` itself, then steps its actor; `"prompt"` means feed
74
+ `content` to your agent, then run `gtd step <actor>` once it's done. gtd itself
75
+ never executes anything — the driver owns running scripts. See
76
+ [STATES.md](STATES.md) for the model and [Driving the loop](docs/loop.md) for
77
+ the full protocol.
75
78
 
76
79
  Along the way, the bundled default workflow develops your sketch into an
77
80
  implementation plan — asking any open question it can't settle itself via a
78
81
  deterministic `.gtd/TODO.md` format, validated before it ever reaches you —
79
82
  builds it, runs your tests (looping on failures), and hands you a
80
83
  `.gtd/REVIEW.md` checkbox review of the cycle's diff: tick a box to approve that
81
- item, or edit/untick for feedback. Approving rests the cycle back at idle, with
82
- every turn commit still sitting in history for you to squash however you like
83
- (or not at all; gtd makes no assumption) — see
84
- [STATES.md](STATES.md#10-the-bundled-default-workflow) for the full shape. A
85
- heavier machine — two-phase Q&A planning, an architecture phase, task
86
- decomposition, a per-task build loop, and a squash finale — is preserved as a
87
- copy-paste `.gtdrc` example at
84
+ item, or edit/untick for feedback. The same review flow also has a direct entry
85
+ point — `gtd review <commitish>` starts a brand new process reviewing
86
+ `<commitish>..HEAD` with no cycle of its own, e.g. a colleague's PR branch.
87
+ Approving rests the cycle back at idle, with every turn commit still sitting in
88
+ history for you to squash however you like (or not at all; gtd makes no
89
+ assumption) — see [STATES.md](STATES.md#10-the-bundled-default-workflow) for the
90
+ full shape. A heavier machine — two-phase Q&A planning, an architecture phase,
91
+ task decomposition, a per-task build loop, and a squash finale — is preserved as
92
+ a copy-paste `.gtdrc` example at
88
93
  [docs/examples/advanced-workflow.md](docs/examples/advanced-workflow.md). The
89
94
  workflow itself is just `.gtdrc` config — swap it for your own (see
90
95
  [Configuration](docs/configuration.md)).
package/bin/gtd-loop CHANGED
@@ -4,9 +4,10 @@ set -euo pipefail
4
4
  # Drives the v3 pattern-machine loop (see docs/loop.md): each iteration asks
5
5
  # `gtd next --json` who's up and what they should do, dispatching on `kind`:
6
6
  # - "message" (a human rest) -> halt, tell the user what gtd is waiting on.
7
- # - "script" (a check rest) -> `gtd run` executes it and steps the check
8
- # actor in one command; zero new commits = green with nothing owed (the
9
- # terminal signal at idle).
7
+ # - "script" (a check rest) -> execute `content` (the emitted wrapper
8
+ # script) verbatim via bash — exit code deliberately ignored, the outcome
9
+ # lives in the tree — then `gtd step <actor>` to capture it; zero new
10
+ # commits = green with nothing owed (the terminal signal at idle).
10
11
  # - "prompt" (an agent rest) -> feed `content` to the agent, run the
11
12
  # self-validation gate (`gtd validate`, re-prompting on findings — see the
12
13
  # gate's own comment below), then `gtd step <actor>` ourselves — the
@@ -86,7 +87,12 @@ while true; do
86
87
 
87
88
  if [[ "$kind" == "script" ]]; then
88
89
  head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
89
- gtd run
90
+ # Execute the emitted wrapper verbatim; its exit code is deliberately
91
+ # ignored (`|| true` under `set -e`) — the script encodes its outcome in
92
+ # the tree, and `gtd step` captures whatever it left there via the state's
93
+ # own `on` patterns.
94
+ bash -c "$content" || true
95
+ gtd step "$actor" >/dev/null
90
96
  head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
91
97
  if [[ "$head_before" == "$head_after" ]]; then
92
98
  echo "--- Settled ($state: check passed, nothing to do) ---"
@@ -189117,6 +189117,10 @@ const isCommitState = (state) => state.commit !== void 0;
189117
189117
  const isReviewWindowState = (def, state) => def.states[state]?.reviewWindow === true;
189118
189118
  /** True when `state` anchors the review window's diff base (see `StateDef.reviewBase`). Safe for an unknown state name (returns `false`). */
189119
189119
  const isReviewBaseState = (def, state) => def.states[state]?.reviewBase === true;
189120
+ /** The workflow's declared review-entry state name (see `StateDef.reviewEntry`) — `gtd review <commitish>` (`src/program.ts`) enters this state to start a new review process. `undefined` when no state declares one; `validateDefinition` guarantees at most one does, so the first (only) match wins. */
189121
+ const reviewEntryStateOf = (def) => {
189122
+ for (const [name, state] of Object.entries(def.states)) if (state.reviewEntry === true) return name;
189123
+ };
189120
189124
  /**
189121
189125
  * `gtd(<actor>): <state>` — the subject a step commit carries. Per decision
189122
189126
  * 2, `<actor>` is WHO AUTHORED THE STEP (the invoker), and `<state>` is the
@@ -189445,6 +189449,29 @@ const validateReviewWindow = (name, state) => {
189445
189449
  if (state.reviewBase !== void 0) errors.push(`state "${name}": a commit state cannot declare "reviewBase"`);
189446
189450
  return errors;
189447
189451
  };
189452
+ /**
189453
+ * `reviewEntry`, when present, is forbidden on a commit state (never at
189454
+ * rest — same rule family as `reviewWindow`/`reviewBase`) and on the initial
189455
+ * state: a review entry is a deliberate, distinct starting point from the
189456
+ * workflow's ordinary "no active cycle" rest (`gtd review` itself REQUIRES
189457
+ * resting at the initial state before it will act — see `src/program.ts` —
189458
+ * so the two must stay distinguishable rather than collapsing into one).
189459
+ * The at-most-one-state rule is checked separately, over the whole
189460
+ * definition (see `validateReviewEntryUniqueness`), since it isn't a
189461
+ * per-state finding.
189462
+ */
189463
+ const validateReviewEntry = (name, state) => {
189464
+ if (state.reviewEntry === void 0) return [];
189465
+ const errors = [];
189466
+ if (isCommitState(state)) errors.push(`state "${name}": a commit state cannot declare "reviewEntry"`);
189467
+ if (state.initial === true) errors.push(`state "${name}": the initial state cannot declare "reviewEntry"`);
189468
+ return errors;
189469
+ };
189470
+ /** At most one state may declare `reviewEntry: true` — `gtd review <commitish>` (`src/program.ts`) needs a single, unambiguous state name to enter (see `reviewEntryStateOf`). */
189471
+ const validateReviewEntryUniqueness = (def, names) => {
189472
+ const entryNames = names.filter((name) => def.states[name].reviewEntry === true);
189473
+ return entryNames.length > 1 ? [`at most one state may declare "reviewEntry" (found ${entryNames.length}: ${entryNames.join(", ")})`] : [];
189474
+ };
189448
189475
  /** Every `on` row parses, and its target names a defined state. */
189449
189476
  const validateOnEdges = (name, state, names) => {
189450
189477
  const errors = [];
@@ -189505,7 +189532,8 @@ const validateState = (def, name, names) => {
189505
189532
  ...validateMemory(name, state),
189506
189533
  ...validateFile(name, state),
189507
189534
  ...validateMode(def, name, state),
189508
- ...validateReviewWindow(name, state)
189535
+ ...validateReviewWindow(name, state),
189536
+ ...validateReviewEntry(name, state)
189509
189537
  ];
189510
189538
  };
189511
189539
  /**
@@ -189527,9 +189555,11 @@ const validateState = (def, name, names) => {
189527
189555
  * `knownModes`), requires a sibling `file`, and is never declared on a commit
189528
189556
  * state; every `modes:` entry declares at least one non-blank
189529
189557
  * `format`/`validate` command; `reviewWindow`/`reviewBase`, when
189530
- * present, are never declared on a commit state; every state is reachable from
189531
- * the initial state by walking `on` targets and `retry.otherwise` redirects
189532
- * (checked only when the initial-state rule itself passed — see
189558
+ * present, are never declared on a commit state; `reviewEntry`, when present,
189559
+ * is never declared on a commit state or the initial state, and at most one
189560
+ * state across the whole workflow may declare it; every state is reachable
189561
+ * from the initial state by walking `on` targets and `retry.otherwise`
189562
+ * redirects (checked only when the initial-state rule itself passed — see
189533
189563
  * `validateReachability`).
189534
189564
  */
189535
189565
  const validateDefinition = (def) => {
@@ -189540,6 +189570,7 @@ const validateDefinition = (def) => {
189540
189570
  ...initialErrors,
189541
189571
  ...validateModes(def),
189542
189572
  ...names.flatMap((name) => validateState(def, name, names)),
189573
+ ...validateReviewEntryUniqueness(def, names),
189543
189574
  ...initialErrors.length === 0 ? validateReachability(def, names) : []
189544
189575
  ];
189545
189576
  };
@@ -189749,7 +189780,8 @@ const KNOWN_STATE_KEYS = /* @__PURE__ */ new Set([
189749
189780
  "file",
189750
189781
  "mode",
189751
189782
  "reviewWindow",
189752
- "reviewBase"
189783
+ "reviewBase",
189784
+ "reviewEntry"
189753
189785
  ]);
189754
189786
  const KNOWN_TOP_KEYS = /* @__PURE__ */ new Set([
189755
189787
  "vars",
@@ -189909,7 +189941,7 @@ const compileMode = (raw, name, errors) => {
189909
189941
  /** The `initial` field: `true` only when the raw value is the literal boolean `true`. */
189910
189942
  const compileInitial = (raw, name, errors) => compileBooleanFlag(raw, "initial", name, errors);
189911
189943
  /**
189912
- * A boolean state flag (`initial`/`reviewWindow`/`reviewBase`): `true` only
189944
+ * A boolean state flag (`initial`/`reviewWindow`/`reviewBase`/`reviewEntry`): `true` only
189913
189945
  * when the raw value is the literal `true`; a non-boolean is a config error;
189914
189946
  * `false` (or absent) compiles away to `undefined` so it never lands in the
189915
189947
  * `StateDef` — `false` and "unset" mean the same thing for every such flag.
@@ -189948,7 +189980,8 @@ const assembleStateDef = (parts) => ({
189948
189980
  file: parts.file,
189949
189981
  mode: parts.mode,
189950
189982
  reviewWindow: parts.reviewWindow,
189951
- reviewBase: parts.reviewBase
189983
+ reviewBase: parts.reviewBase,
189984
+ reviewEntry: parts.reviewEntry
189952
189985
  }),
189953
189986
  ...assembleContentFields(parts.content)
189954
189987
  });
@@ -189971,7 +190004,8 @@ const compileState = (name, raw, configDir, errors) => {
189971
190004
  file: compileFile(raw, name, errors),
189972
190005
  mode: compileMode(raw, name, errors),
189973
190006
  reviewWindow: compileBooleanFlag(raw, "reviewWindow", name, errors),
189974
- reviewBase: compileBooleanFlag(raw, "reviewBase", name, errors)
190007
+ reviewBase: compileBooleanFlag(raw, "reviewBase", name, errors),
190008
+ reviewEntry: compileBooleanFlag(raw, "reviewEntry", name, errors)
189975
190009
  });
189976
190010
  };
189977
190011
  /**
@@ -190028,7 +190062,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes) => {
190028
190062
  * default.yaml's content values start with `./`/`../`, so
190029
190063
  * `compileWorkflowConfig` never resolves a file reference against it.
190030
190064
  */
190031
- const compiled = compileWorkflowConfig((0, import_dist$1.parse)("# The bundled default gtd workflow (v3 \"pattern machine\" — see\n# docs/design/pattern-machine-plan.md, and\n# docs/design/steering-file-validation-command.md for the `gtd validate`\n# self-validation model that replaced the old in-machine validation loops).\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/default.ts does the compiling).\n#\n# Every content string below is INLINE (no `./`-relative file references):\n# the bundled default ships inside a single-file build\n# (`dist/gtd.bundle.mjs`), so its content must already be resolved at BUILD\n# time rather than read from disk at RUNTIME — the same reason\n# `src/prompts/*.md` are imported through tsdown's `.md`-as-text loader\n# instead of read via `fs` at runtime. `default.yaml` itself is imported the\n# same way (tsdown/vitest both treat `.yaml` as text — see\n# tsdown.config.ts / tests/vitest.rawMd.ts), so this file never needs a\n# sibling prompts/ directory to exist on disk once installed.\n#\n# States, in cycle order — 10 states, no squash:\n# idle (initial, human, message)\n# -> grilling (agent, prompt): reads .gtd/TODO.md's sketch (or a prior\n# lap's feedback), explores the codebase, and develops it into a concrete\n# implementation plan, stating any open question it can't settle itself\n# under a `## Open Questions` heading rather than guessing silently. Its\n# `file:`/`mode:` (TODO.md/`qa`) makes the plan's format checkable — it\n# self-validates before finishing (see \"Steering-file validation\" below).\n# grilling -> grilling-answer (human, message): tick off suggested\n# defaults by replacing them with `Answer:` lines; a clean step accepts\n# every remaining default -> building; any edit loops back to grilling.\n# grilling-answer -> building (agent, prompt): implements the plan in\n# .gtd/TODO.md directly (no task decomposition, no per-task queue) and\n# deletes .gtd/TODO.md once the work is complete and verified.\n# building -> checking (check, script): runs the test command inline;\n# A/M .gtd/FEEDBACK.md -> fixing (capped, redirects to escalate); D\n# .gtd/FEEDBACK.md (a green run that just cleaned up prior feedback) or C\n# (green, nothing to clean) -> reviewing.\n# fixing (agent, prompt) <-> checking\n# escalate (human, message): manual intervention, then back to checking\n# checking (green) -> reviewing (agent, prompt): writes .gtd/REVIEW.md in\n# the checkbox review format, grouping the cycle's full diff into\n# reviewable chunks; its `file:`/`mode:` (REVIEW.md/`review`) makes that\n# format checkable, so it self-validates before finishing.\n# reviewing -> await-review (human, message): tick a pointer's `- [\n# ]` to `- [x]` to approve that item; deleting .gtd/REVIEW.md outright is\n# the power-user approve-everything shortcut; anything else pending\n# (code edits with REVIEW.md untouched) is feedback straight to grilling.\n# await-review -> review-deciding (check, script), entered by any `M\n# .gtd/REVIEW.md` step: if no unticked `- [ ]` pointer remains, the cycle\n# is approved (rm .gtd/REVIEW.md -> idle); otherwise the still-unticked\n# pointers (with their notes) are extracted into a fresh .gtd/TODO.md and\n# .gtd/REVIEW.md is removed (-> grilling, feedback lap).\n#\n# The cycle ends at human approval, resting back at idle — there is no\n# squash state. The cycle's commits stay in history; whether/how to squash\n# them (amend, interactive rebase, a PR's squash-merge) is entirely the\n# human's business once they're done reviewing — gtd makes no assumption.\n# That squash-flavored finale (a `squashing` prompt state + a `done` commit\n# state) still exists as an ENGINE capability (see STATES.md §8) and is kept\n# as part of the fuller machine at docs/examples/advanced-workflow.md.\n#\n# A `gtd(<actor>): idle` commit — entering the workflow's own initial state —\n# is therefore a PROCESS BOUNDARY (see `computeProcessRun` in src/Edge.ts):\n# the next cycle's `retry` counts, `startCommit`, and diffs never reach back\n# across an approved cycle's own idle-entering commit.\n#\n# HYGIENE INVARIANT (asserted in e2e — see default-workflow.feature): an\n# approved cycle leaves `.gtd/` empty. FEEDBACK.md is cleaned up by a green\n# `checking` run; REVIEW.md by the review-deciding approval branch; TODO.md by\n# `building`.\n#\n# Steering-file validation. `grilling` and `reviewing` each declare a\n# `file:`/`mode:` pair (TODO.md as `qa`, REVIEW.md as `review`), so their\n# output has a checkable format. src/OpenQuestions.ts and src/ReviewDoc.ts are\n# those two formats' EXECUTABLE SPECS (their unit tests are the format spec\n# tests), and `gtd validate` FORMATS the resolved state's rendered `file:` in\n# place then runs the SAME parser over it, exiting non-zero with findings when\n# it violates the format. There is no in-machine validation state and no bash\n# port to keep in sync — the producing agent self-validates before finishing\n# (plain `gtd next` appends the \"run `gtd validate` and fix all violations\"\n# instruction; under `--json` the driving loop runs it after the turn and\n# re-prompts on findings — see skills/loop/SKILL.md), AND `gtd step` runs the\n# same format-and-validate gate on capture, refusing a turn whose steering file\n# is invalid. So the check runs after a human edits too — at the human gates\n# `grilling-answer` (TODO.md) and `await-review` (REVIEW.md) — and a human gate\n# is only ever handed a tidy, well-formed file. The same parsers back the LSP's\n# live diagnostics (src/Lsp.ts).\n#\n# `grilling` and `reviewing` both declare `model: smart` — an opaque harness\n# hint (see the comment at `grilling` below) for their heavier one-shot\n# planning/reviewing turns. Every other state is unset (harness default).\n#\n# The four agent states each declare a `memory:` scope label (`plan`,\n# `build`, `fix`, `review` — see the comment at `grilling` below) — another\n# opaque hint a memory-aware driver reads to decide when an agent turn keeps\n# the previous turn's memory and when it starts fresh: a loop that re-enters\n# one state (grilling around its answer loop, fixing around checking) keeps\n# the same label and retains memory across its laps; crossing to the next\n# phase's differently-labelled state clears it. gtd never interprets the\n# label (see STATES.md §1 and skills/loop/SKILL.md's driver contract).\n#\n# `vars.testCommand` is this workflow's own declared default for `checking`'s\n# script (see it.vars in PatternTemplates.ts) — override it per-repo with a\n# top-level `.gtdrc` `vars: { testCommand: ... }` key, or per-invocation with\n# a `GTD_VAR_testCommand` environment variable (highest precedence).\n#\n# `vars.todoFile`/`reviewFile`/`feedbackFile` are this workflow's ONE source\n# of truth for the three steering-file paths every prompt/script below reads\n# or writes — each appears in templates as `<%~ it.vars.todoFile %>` etc\n# (see docs/design/state-file-association.md §2). Every state that rests on\n# one of these files also declares a `file:`/`mode:` pair (see STATES.md's\n# property table) so `gtd status`/`gtd next --json` and the LSP (`gtd lsp`)\n# know which file a human/editor should look at, and in what format\n# (`qa` for TODO.md's open-questions format, `review` for REVIEW.md's\n# checkbox format) — `fixing`/`escalate` declare `file:` alone (FEEDBACK.md\n# is plain text, no LSP format). KNOWN LIMITATION: `on` pattern keys below are\n# NOT Eta templates — they keep the LITERAL `.gtd/...` paths matching these\n# vars' DEFAULT values. Repointing `todoFile`/`reviewFile`/`feedbackFile` via\n# `.gtdrc`'s top-level `vars:` or a `GTD_VAR_` override changes what `file:`\n# renders to (and what a template reads/writes) WITHOUT changing what the\n# `on` patterns match against — desyncing the machine. See\n# docs/configuration.md's `file:`/`mode:` section for the full caveat.\n#\n# A fuller machine — two-phase Q&A planning, an architecture phase, task\n# decomposition, a deterministic per-task builder loop, agent-prepared\n# review, and the squash finale (`squashing` + `done`) — is preserved as a\n# copy-paste `.gtdrc` example at docs/examples/advanced-workflow.md.\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n reviewFile: .gtd/REVIEW.md\n feedbackFile: .gtd/FEEDBACK.md\n\nstates:\n idle:\n actor: human\n initial: true\n # Every human-gate `message:` below closes with the SAME rendered block —\n # a \"What each change does next\" list built from `it.edges`, the resting\n # state's own `on` edges (see PatternTemplates.TemplateContext.edges). Each\n # edge's human-readable route lives in its `describe:` (the single source\n # of truth), so the list can never drift from the routing it documents.\n # The block skips edges without a `describe`, and each `<%~ ... \"\\n\" %>`\n # keeps the bullet's own newline (Eta's autoTrim strips the template's own\n # line breaks around `<% %>` tags, not text inside an interpolation).\n message: |\n No active gtd cycle.\n\n To start one, write `.gtd/TODO.md` with a short sketch of what you want\n built — a few sentences is enough.\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: grilling\n describe: >-\n Save your sketch to `.gtd/TODO.md` (any working-tree change) to start\n the cycle — the agent reads it, explores the codebase, and develops it\n into a concrete implementation plan (**grilling**).\n\n grilling:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # `model` is an opaque harness hint — gtd never interprets this string, it\n # only passes it through to `gtd next --json`/`gtd status --json` so the\n # driving loop can map it onto whatever models its agent harness\n # provides. \"smart\" here just names the harness-chosen tier for the\n # heavier planning turn; states without a `model` use the harness's\n # default.\n model: smart\n # `memory` is an opaque memory-scope label — gtd never interprets it, it\n # only passes it through to `gtd next --json`/`gtd status --json` so a\n # memory-aware driving loop can decide when an agent turn continues from\n # the previous turn's memory and when it starts fresh: consecutive agent\n # turns that emit the SAME label share memory, a change (or the first\n # turn) is where the driver starts fresh. `grilling` re-enters itself\n # around the grilling-answer loop, so labelling every lap\n # `plan` keeps the planner's accumulated exploration across the loop; the\n # next phase (`building`) carries a different label, clearing it.\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete implementation plan.\n\n For every question you cannot settle yourself, add it under a\n `## Open Questions` heading, one `### <question>` sub-heading each,\n whose body's FIRST non-blank line is exactly\n `Suggested default: <your answer>` — the human can accept it as-is or\n override it. Omit the section entirely once there are no open\n questions. If `<%= it.vars.todoFile %>` already carries\n `## Open Questions` entries from a previous lap, fold any\n `Answer: <text>` line into the plan; every remaining entry's body must\n still start with either `Suggested default: ...` or `Answer: ...` —\n never anything else.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": grilling-answer\n\n grilling-answer:\n actor: human\n file: <%= it.vars.todoFile %>\n mode: qa\n message: |\n `<%= it.vars.todoFile %>` holds the plan under development, with any\n open questions under `## Open Questions` — each carrying a suggested\n default. Answer a question by editing its entry in place: replace its\n `Suggested default: ...` line with `Answer: ...`, or add further detail.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: building\n describe: >-\n Change nothing to accept every suggested default as-is and move on to\n implementation (**building**).\n \"* **\":\n to: grilling\n describe: >-\n Edit the plan — answer a question, add detail, or raise a new one — to\n send it back for another round of grilling (**grilling**).\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # A distinct memory scope from `plan`: implementation starts fresh, with\n # the plan already settled in `.gtd/TODO.md` (which this prompt reads\n # below) rather than carried in the planner's working memory.\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\n its 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\n all 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 checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: run the test suite and record a\n # red run's output as .gtd/FEEDBACK.md. What that output MEANS (another\n # fix round vs escalation) is decided by the workflow's own `on`/`retry`\n # rules at capture time — never here.\n #\n # `it.vars.testCommand` defaults to \"npm test\" (this workflow's own\n # `vars:` above) — override it with a top-level `.gtdrc`\n # `vars: { testCommand: ... }` key, or a `GTD_VAR_testCommand`\n # environment variable (highest precedence).\n #\n # A green run also removes any `<%= it.vars.feedbackFile %>` left over\n # from an earlier red run — there is no squash to discard it for us\n # anymore, so the script cleans it up itself before moving on to\n # review.\n set +e\n mkdir -p .gtd\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 <%~ it.vars.feedbackFile %>\n else\n rm -f .gtd/.check-output\n printf 'npm test failed with exit code %s and produced no output.' \"$code\" > <%~ it.vars.feedbackFile %>\n fi\n else\n rm -f .gtd/.check-output\n rm -f <%~ it.vars.feedbackFile %>\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n # Its own memory scope: the fix loop re-enters `fixing` around `checking`\n # (up to the retry cap), so labelling every attempt `fix` lets the agent\n # remember what it already tried across the loop; a green check moves on\n # to the differently-labelled `reviewing`, clearing it.\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\n the code so the suite passes. Keep the change focused — do not\n refactor unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue; the\n machine picks the dispute up either way.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: checking\n describe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: smart\n # A distinct memory scope again: the reviewer looks at the cycle's full\n # diff (inlined into its prompt) with a fresh perspective, not the\n # builder's or fixer's working memory.\n memory: review\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 cycle's\n full diff (inlined below) in 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\n of what changed and why, then one\n `- [ ] ./path/to/file.ts#42 — note` file pointer per hunk (checkboxes\n are for the human to tick, not you; every path is `./`-relative, with\n an optional `#line` and an optional ` — note`).\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Full cycle diff\n\n ```diff\n <%~ it.processDiff %>\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 for human review, gtd opens a \"review checkout\n # window\": HEAD and the index are temporarily rewound to this cycle's start\n # (the process boundary — no `reviewBase` state is declared, so the base\n # defaults to it) with the working tree untouched, so the whole cycle diff\n # surfaces as ordinary uncommitted changes in the editor's git integration.\n # The window closes automatically on the next invocation, once the cycle\n # rests anywhere else (see src/ReviewWindow.ts, STATES.md §11).\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the completed\n cycle — one `- [ ]` checkbox per reviewable item, grouped into chunks.\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 \"D .gtd/REVIEW.md\":\n to: idle\n describe: >-\n Delete `.gtd/REVIEW.md` outright to approve the whole cycle at once\n and rest back at the start (**idle**).\n \"M .gtd/REVIEW.md\":\n to: review-deciding\n describe: >-\n Edit `.gtd/REVIEW.md` — tick a `- [ ]` box to `- [x]` to approve that\n item, or add notes — to hand it to a deterministic check\n (**review-deciding**) that closes the cycle out when every box is\n ticked, or extracts the still-unticked items into a fresh\n `.gtd/TODO.md` and loops back through **grilling** with that feedback.\n \"* **\":\n to: grilling\n describe: >-\n Change only code, leaving `.gtd/REVIEW.md` untouched, to send that as\n feedback straight back to **grilling**.\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: if .gtd/REVIEW.md still has any\n # unticked \"- [ ]\" pointer, extract those pointers (with their notes)\n # into a fresh .gtd/TODO.md; either way, remove .gtd/REVIEW.md. What\n # the resulting diff MEANS (feedback lap vs. full approval) is decided\n # by this state's own `on` rules at capture time — never here. The\n # A/M .gtd/TODO.md row is declared BEFORE the D .gtd/REVIEW.md row (see\n # this workflow's `on` map below) so a feedback lap — which always also\n # deletes .gtd/REVIEW.md — is captured as feedback, not mistaken for an\n # approval.\n set +e\n mkdir -p .gtd\n if grep -qE '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %> 2>/dev/null; then\n {\n echo \"Feedback from review — address these before continuing:\"\n echo\n grep -E '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %>\n } > <%~ it.vars.todoFile %>\n fi\n rm -f <%~ it.vars.reviewFile %>\n on:\n \"A .gtd/TODO.md\": grilling\n \"M .gtd/TODO.md\": grilling\n \"D .gtd/REVIEW.md\": idle\n \"C\": await-review\n"), ".");
190065
+ const compiled = compileWorkflowConfig((0, import_dist$1.parse)("# The bundled default gtd workflow (v3 \"pattern machine\" — see\n# docs/design/pattern-machine-plan.md, and\n# docs/design/steering-file-validation-command.md for the `gtd validate`\n# self-validation model that replaced the old in-machine validation loops).\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/default.ts does the compiling).\n#\n# Every content string below is INLINE (no `./`-relative file references):\n# the bundled default ships inside a single-file build\n# (`dist/gtd.bundle.mjs`), so its content must already be resolved at BUILD\n# time rather than read from disk at RUNTIME — the same reason\n# `src/prompts/*.md` are imported through tsdown's `.md`-as-text loader\n# instead of read via `fs` at runtime. `default.yaml` itself is imported the\n# same way (tsdown/vitest both treat `.yaml` as text — see\n# tsdown.config.ts / tests/vitest.rawMd.ts), so this file never needs a\n# sibling prompts/ directory to exist on disk once installed.\n#\n# States, in cycle order — 10 states, no squash:\n# idle (initial, human, message)\n# -> grilling (agent, prompt): reads .gtd/TODO.md's sketch (or a prior\n# lap's feedback), explores the codebase, and develops it into a concrete\n# implementation plan, stating any open question it can't settle itself\n# under a `## Open Questions` heading rather than guessing silently. Its\n# `file:`/`mode:` (TODO.md/`qa`) makes the plan's format checkable — it\n# self-validates before finishing (see \"Steering-file validation\" below).\n# grilling -> grilling-answer (human, message): tick off suggested\n# defaults by replacing them with `Answer:` lines; a clean step accepts\n# every remaining default -> building; any edit loops back to grilling.\n# grilling-answer -> building (agent, prompt): implements the plan in\n# .gtd/TODO.md directly (no task decomposition, no per-task queue) and\n# deletes .gtd/TODO.md once the work is complete and verified.\n# building -> checking (check, script): runs the test command inline;\n# A/M .gtd/FEEDBACK.md -> fixing (capped, redirects to escalate); D\n# .gtd/FEEDBACK.md (a green run that just cleaned up prior feedback) or C\n# (green, nothing to clean) -> reviewing.\n# fixing (agent, prompt) <-> checking\n# escalate (human, message): manual intervention, then back to checking\n# checking (green) -> reviewing (agent, prompt): writes .gtd/REVIEW.md in\n# the checkbox review format, grouping the cycle's full diff into\n# reviewable chunks; its `file:`/`mode:` (REVIEW.md/`review`) makes that\n# format checkable, so it self-validates before finishing. `reviewing`\n# ALSO declares `reviewEntry: true` — a second way in: `gtd review\n# <commitish>` (a clean tree resting at idle) writes an empty\n# `gtd(human): reviewing` entry commit carrying <commitish>'s hash as a\n# `Gtd-Review-Base:` trailer, starting a brand new process that reviews\n# <commitish>..HEAD (e.g. a colleague's PR branch) through this exact same\n# reviewing/await-review/feedback-lap machinery.\n# reviewing -> await-review (human, message): tick a pointer's `- [\n# ]` to `- [x]` to approve that item; deleting .gtd/REVIEW.md outright is\n# the power-user approve-everything shortcut; anything else pending\n# (code edits with REVIEW.md untouched) is feedback straight to grilling.\n# await-review -> review-deciding (check, script), entered by any `M\n# .gtd/REVIEW.md` step: if no unticked `- [ ]` pointer remains, the cycle\n# is approved (rm .gtd/REVIEW.md -> idle); otherwise the still-unticked\n# pointers (with their notes) are extracted into a fresh .gtd/TODO.md and\n# .gtd/REVIEW.md is removed (-> grilling, feedback lap).\n#\n# The cycle ends at human approval, resting back at idle — there is no\n# squash state. The cycle's commits stay in history; whether/how to squash\n# them (amend, interactive rebase, a PR's squash-merge) is entirely the\n# human's business once they're done reviewing — gtd makes no assumption.\n# That squash-flavored finale (a `squashing` prompt state + a `done` commit\n# state) still exists as an ENGINE capability (see STATES.md §8) and is kept\n# as part of the fuller machine at docs/examples/advanced-workflow.md.\n#\n# A `gtd(<actor>): idle` commit — entering the workflow's own initial state —\n# is therefore a PROCESS BOUNDARY (see `computeProcessRun` in src/Edge.ts):\n# the next cycle's `retry` counts, `startCommit`, and diffs never reach back\n# across an approved cycle's own idle-entering commit.\n#\n# HYGIENE INVARIANT (asserted in e2e — see default-workflow.feature): an\n# approved cycle leaves `.gtd/` empty. FEEDBACK.md is cleaned up by a green\n# `checking` run; REVIEW.md by the review-deciding approval branch; TODO.md by\n# `building`.\n#\n# Steering-file validation. `grilling` and `reviewing` each declare a\n# `file:`/`mode:` pair (TODO.md as `qa`, REVIEW.md as `review`), so their\n# output has a checkable format. src/OpenQuestions.ts and src/ReviewDoc.ts are\n# those two formats' EXECUTABLE SPECS (their unit tests are the format spec\n# tests), and `gtd validate` FORMATS the resolved state's rendered `file:` in\n# place then runs the SAME parser over it, exiting non-zero with findings when\n# it violates the format. There is no in-machine validation state and no bash\n# port to keep in sync — the producing agent self-validates before finishing\n# (plain `gtd next` appends the \"run `gtd validate` and fix all violations\"\n# instruction; under `--json` the driving loop runs it after the turn and\n# re-prompts on findings — see skills/loop/SKILL.md), AND `gtd step` runs the\n# same format-and-validate gate on capture, refusing a turn whose steering file\n# is invalid. So the check runs after a human edits too — at the human gates\n# `grilling-answer` (TODO.md) and `await-review` (REVIEW.md) — and a human gate\n# is only ever handed a tidy, well-formed file. The same parsers back the LSP's\n# live diagnostics (src/Lsp.ts).\n#\n# `grilling` and `reviewing` both declare `model: smart` — an opaque harness\n# hint (see the comment at `grilling` below) for their heavier one-shot\n# planning/reviewing turns. Every other state is unset (harness default).\n#\n# The four agent states each declare a `memory:` scope label (`plan`,\n# `build`, `fix`, `review` — see the comment at `grilling` below) — another\n# opaque hint a memory-aware driver reads to decide when an agent turn keeps\n# the previous turn's memory and when it starts fresh: a loop that re-enters\n# one state (grilling around its answer loop, fixing around checking) keeps\n# the same label and retains memory across its laps; crossing to the next\n# phase's differently-labelled state clears it. gtd never interprets the\n# label (see STATES.md §1 and skills/loop/SKILL.md's driver contract).\n#\n# `vars.testCommand` is this workflow's own declared default for `checking`'s\n# script (see it.vars in PatternTemplates.ts) — override it per-repo with a\n# top-level `.gtdrc` `vars: { testCommand: ... }` key, or per-invocation with\n# a `GTD_VAR_testCommand` environment variable (highest precedence).\n#\n# `vars.todoFile`/`reviewFile`/`feedbackFile` are this workflow's ONE source\n# of truth for the three steering-file paths every prompt/script below reads\n# or writes — each appears in templates as `<%~ it.vars.todoFile %>` etc\n# (see docs/design/state-file-association.md §2). Every state that rests on\n# one of these files also declares a `file:`/`mode:` pair (see STATES.md's\n# property table) so `gtd status`/`gtd next --json` and the LSP (`gtd lsp`)\n# know which file a human/editor should look at, and in what format\n# (`qa` for TODO.md's open-questions format, `review` for REVIEW.md's\n# checkbox format) — `fixing`/`escalate` declare `file:` alone (FEEDBACK.md\n# is plain text, no LSP format). KNOWN LIMITATION: `on` pattern keys below are\n# NOT Eta templates — they keep the LITERAL `.gtd/...` paths matching these\n# vars' DEFAULT values. Repointing `todoFile`/`reviewFile`/`feedbackFile` via\n# `.gtdrc`'s top-level `vars:` or a `GTD_VAR_` override changes what `file:`\n# renders to (and what a template reads/writes) WITHOUT changing what the\n# `on` patterns match against — desyncing the machine. See\n# docs/configuration.md's `file:`/`mode:` section for the full caveat.\n#\n# A fuller machine — two-phase Q&A planning, an architecture phase, task\n# decomposition, a deterministic per-task builder loop, agent-prepared\n# review, and the squash finale (`squashing` + `done`) — is preserved as a\n# copy-paste `.gtdrc` example at docs/examples/advanced-workflow.md.\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n reviewFile: .gtd/REVIEW.md\n feedbackFile: .gtd/FEEDBACK.md\n\nstates:\n idle:\n actor: human\n initial: true\n # Every human-gate `message:` below closes with the SAME rendered block —\n # a \"What each change does next\" list built from `it.edges`, the resting\n # state's own `on` edges (see PatternTemplates.TemplateContext.edges). Each\n # edge's human-readable route lives in its `describe:` (the single source\n # of truth), so the list can never drift from the routing it documents.\n # The block skips edges without a `describe`, and each `<%~ ... \"\\n\" %>`\n # keeps the bullet's own newline (Eta's autoTrim strips the template's own\n # line breaks around `<% %>` tags, not text inside an interpolation).\n message: |\n No active gtd cycle.\n\n To start one, write `.gtd/TODO.md` with a short sketch of what you want\n built — a few sentences is enough.\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: grilling\n describe: >-\n Save your sketch to `.gtd/TODO.md` (any working-tree change) to start\n the cycle — the agent reads it, explores the codebase, and develops it\n into a concrete implementation plan (**grilling**).\n\n grilling:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # `model` is an opaque harness hint — gtd never interprets this string, it\n # only passes it through to `gtd next --json`/`gtd status --json` so the\n # driving loop can map it onto whatever models its agent harness\n # provides. \"smart\" here just names the harness-chosen tier for the\n # heavier planning turn; states without a `model` use the harness's\n # default.\n model: smart\n # `memory` is an opaque memory-scope label — gtd never interprets it, it\n # only passes it through to `gtd next --json`/`gtd status --json` so a\n # memory-aware driving loop can decide when an agent turn continues from\n # the previous turn's memory and when it starts fresh: consecutive agent\n # turns that emit the SAME label share memory, a change (or the first\n # turn) is where the driver starts fresh. `grilling` re-enters itself\n # around the grilling-answer loop, so labelling every lap\n # `plan` keeps the planner's accumulated exploration across the loop; the\n # next phase (`building`) carries a different label, clearing it.\n memory: plan\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete implementation plan.\n\n For every question you cannot settle yourself, add it under a\n `## Open Questions` heading, one `### <question>` sub-heading each,\n whose body's FIRST non-blank line is exactly\n `Suggested default: <your answer>` — the human can accept it as-is or\n override it. Omit the section entirely once there are no open\n questions. If `<%= it.vars.todoFile %>` already carries\n `## Open Questions` entries from a previous lap, fold any\n `Answer: <text>` line into the plan; every remaining entry's body must\n still start with either `Suggested default: ...` or `Answer: ...` —\n never anything else.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": grilling-answer\n\n grilling-answer:\n actor: human\n file: <%= it.vars.todoFile %>\n mode: qa\n message: |\n `<%= it.vars.todoFile %>` holds the plan under development, with any\n open questions under `## Open Questions` — each carrying a suggested\n default. Answer a question by editing its entry in place: replace its\n `Suggested default: ...` line with `Answer: ...`, or add further detail.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: building\n describe: >-\n Change nothing to accept every suggested default as-is and move on to\n implementation (**building**).\n \"* **\":\n to: grilling\n describe: >-\n Edit the plan — answer a question, add detail, or raise a new one — to\n send it back for another round of grilling (**grilling**).\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # A distinct memory scope from `plan`: implementation starts fresh, with\n # the plan already settled in `.gtd/TODO.md` (which this prompt reads\n # below) rather than carried in the planner's working memory.\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\n its 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\n all 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 checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the loop driver executes this verbatim, then\n # steps the check actor. Mechanics only: run the test suite and record a\n # red run's output as .gtd/FEEDBACK.md. What that output MEANS (another\n # fix round vs escalation) is decided by the workflow's own `on`/`retry`\n # rules at capture time — never here.\n #\n # `it.vars.testCommand` defaults to \"npm test\" (this workflow's own\n # `vars:` above) — override it with a top-level `.gtdrc`\n # `vars: { testCommand: ... }` key, or a `GTD_VAR_testCommand`\n # environment variable (highest precedence).\n #\n # A green run also removes any `<%= it.vars.feedbackFile %>` left over\n # from an earlier red run — there is no squash to discard it for us\n # anymore, so the script cleans it up itself before moving on to\n # review.\n set +e\n mkdir -p .gtd\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 <%~ it.vars.feedbackFile %>\n else\n rm -f .gtd/.check-output\n printf 'npm test failed with exit code %s and produced no output.' \"$code\" > <%~ it.vars.feedbackFile %>\n fi\n else\n rm -f .gtd/.check-output\n rm -f <%~ it.vars.feedbackFile %>\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n # Its own memory scope: the fix loop re-enters `fixing` around `checking`\n # (up to the retry cap), so labelling every attempt `fix` lets the agent\n # remember what it already tried across the loop; a green check moves on\n # to the differently-labelled `reviewing`, clearing it.\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\n the code so the suite passes. Keep the change focused — do not\n refactor unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue; the\n machine picks the dispute up either way.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: checking\n describe: >-\n Fix the failure yourself — edit the code and/or `.gtd/FEEDBACK.md` —\n to re-run the check (**checking**).\n\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: smart\n # A distinct memory scope again: the reviewer looks at the cycle's full\n # diff (inlined into its prompt) with a fresh perspective, not the\n # builder's or fixer's working memory.\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # enters here directly, recording the resolved base as a `Gtd-Review-Base:`\n # trailer on its empty entry commit — everything below then reuses the\n # existing reviewing/await-review/feedback-lap machinery unmodified over\n # <commitish>..HEAD (see STATES.md §11).\n reviewEntry: true\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 cycle's\n full diff (inlined below) in 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\n of what changed and why, then one\n `- [ ] ./path/to/file.ts#42 — note` file pointer per hunk (checkboxes\n are for the human to tick, not you; every path is `./`-relative, with\n an optional `#line` and an optional ` — note`).\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Full cycle diff\n\n ```diff\n <%~ it.processDiff %>\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 for human review, gtd opens a \"review checkout\n # window\": HEAD and the index are temporarily rewound to this cycle's start\n # (the process boundary — no `reviewBase` state is declared, so the base\n # defaults to it) with the working tree untouched, so the whole cycle diff\n # surfaces as ordinary uncommitted changes in the editor's git integration.\n # The window closes automatically on the next invocation, once the cycle\n # rests anywhere else (see src/ReviewWindow.ts, STATES.md §11).\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the completed\n cycle — one `- [ ]` checkbox per reviewable item, grouped into chunks.\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 \"D .gtd/REVIEW.md\":\n to: idle\n describe: >-\n Delete `.gtd/REVIEW.md` outright to approve the whole cycle at once\n and rest back at the start (**idle**).\n \"M .gtd/REVIEW.md\":\n to: review-deciding\n describe: >-\n Edit `.gtd/REVIEW.md` — tick a `- [ ]` box to `- [x]` to approve that\n item, or add notes — to hand it to a deterministic check\n (**review-deciding**) that closes the cycle out when every box is\n ticked, or extracts the still-unticked items into a fresh\n `.gtd/TODO.md` and loops back through **grilling** with that feedback.\n \"* **\":\n to: grilling\n describe: >-\n Change only code, leaving `.gtd/REVIEW.md` untouched, to send that as\n feedback straight back to **grilling**.\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the loop driver executes this verbatim, then\n # steps the check actor. Mechanics only: if .gtd/REVIEW.md still has any\n # unticked \"- [ ]\" pointer, extract those pointers (with their notes)\n # into a fresh .gtd/TODO.md; either way, remove .gtd/REVIEW.md. What\n # the resulting diff MEANS (feedback lap vs. full approval) is decided\n # by this state's own `on` rules at capture time — never here. The\n # A/M .gtd/TODO.md row is declared BEFORE the D .gtd/REVIEW.md row (see\n # this workflow's `on` map below) so a feedback lap — which always also\n # deletes .gtd/REVIEW.md — is captured as feedback, not mistaken for an\n # approval.\n set +e\n mkdir -p .gtd\n if grep -qE '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %> 2>/dev/null; then\n {\n echo \"Feedback from review — address these before continuing:\"\n echo\n grep -E '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %>\n } > <%~ it.vars.todoFile %>\n fi\n rm -f <%~ it.vars.reviewFile %>\n on:\n \"A .gtd/TODO.md\": grilling\n \"M .gtd/TODO.md\": grilling\n \"D .gtd/REVIEW.md\": idle\n \"C\": await-review\n"), ".");
190032
190066
  const defaultWorkflowDefinition = compiled.definition;
190033
190067
  /** The default workflow's own declared `vars:` defaults — `{ testCommand: "npm test" }`, overridable via a top-level `.gtdrc` `vars:` key or a `GTD_VAR_testCommand` environment variable (see `src/Edge.ts`'s `resolveVars`). */
190034
190068
  const defaultWorkflowVars = compiled.vars;
@@ -190130,7 +190164,7 @@ const workflowJsonSchema = {
190130
190164
  },
190131
190165
  script: {
190132
190166
  type: "string",
190133
- description: "Content kind: a shell script (Eta template). `gtd run` executes it verbatim via bash, then steps this state's actor."
190167
+ description: "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
190134
190168
  },
190135
190169
  prompt: {
190136
190170
  type: "string",
@@ -190210,6 +190244,10 @@ const workflowJsonSchema = {
190210
190244
  reviewBase: {
190211
190245
  type: "boolean",
190212
190246
  description: "Marks the state whose most-recent in-process commit anchors the review window's diff base; absent any, the base is the process start. Forbidden on a commit state."
190247
+ },
190248
+ reviewEntry: {
190249
+ type: "boolean",
190250
+ description: "Marks the (at most one) state `gtd review <commitish>` enters to start a brand new process reviewing <commitish>..HEAD. Forbidden on a commit state and on the initial state."
190213
190251
  }
190214
190252
  }
190215
190253
  }
@@ -191952,6 +191990,23 @@ const parseCostTrailers = (messages) => {
191952
191990
  }
191953
191991
  return entries;
191954
191992
  };
191993
+ const REVIEW_BASE_TRAILER_PREFIX = "Gtd-Review-Base: ";
191994
+ const REVIEW_BASE_TRAILER_RE = /^Gtd-Review-Base:[ \t]*(\S+)[ \t]*$/m;
191995
+ /**
191996
+ * Append a `Gtd-Review-Base: <base>` trailer (after a blank line) to a commit
191997
+ * `subject` — the entry commit `gtd review <commitish>` writes to start a new
191998
+ * review process. Mirrors `withCostTrailer`'s placement: the subject (first
191999
+ * line) is untouched, so `parseStateSubject`/`resolveState` read it back
192000
+ * exactly like any other turn commit.
192001
+ */
192002
+ const withReviewBaseTrailer = (subject, base) => `${subject}\n\n${REVIEW_BASE_TRAILER_PREFIX}${base}`;
192003
+ /**
192004
+ * The `Gtd-Review-Base: <hash>` trailer recorded on a `gtd review <commitish>`
192005
+ * entry commit (see `withReviewBaseTrailer`), or `undefined` when `message`
192006
+ * carries none. Read back by `computeProcessRun` — ONLY off the process's
192007
+ * first (oldest) commit — to override the run's diff base.
192008
+ */
192009
+ const parseReviewBaseTrailer = (message) => REVIEW_BASE_TRAILER_RE.exec(message)?.[1];
191955
192010
  /** The total token cost across the given entries (`0` when none). */
191956
192011
  const totalCostOf = (entries) => entries.reduce((sum, entry) => sum + entry.cost, 0);
191957
192012
  /** Per-model token totals, highest-cost first (ties broken by model name for a stable order). */
@@ -192007,6 +192062,7 @@ const computeProcessRun = (git, def) => gen(function* () {
192007
192062
  if (!(yield* git.hasCommits())) return {
192008
192063
  startHash: "",
192009
192064
  startParentHash: EMPTY_TREE$1,
192065
+ diffBase: EMPTY_TREE$1,
192010
192066
  trace: [],
192011
192067
  costEntries: []
192012
192068
  };
@@ -192026,6 +192082,7 @@ const computeProcessRun = (git, def) => gen(function* () {
192026
192082
  return {
192027
192083
  startHash: startIdx < history.length ? history[startIdx].hash : history[history.length - 1].hash,
192028
192084
  startParentHash,
192085
+ diffBase: (processCommits.length > 0 ? parseReviewBaseTrailer(processCommits[0].message) : void 0) ?? startParentHash,
192029
192086
  trace,
192030
192087
  costEntries
192031
192088
  };
@@ -192078,7 +192135,7 @@ const buildTemplateContext = (git, read, state, actor, run, vars, edges, current
192078
192135
  const hasCommits = yield* git.hasCommits();
192079
192136
  const currentCommit = hasCommits ? yield* git.resolveRef("HEAD") : "";
192080
192137
  const previousCommit = hasCommits ? yield* git.resolveRef("HEAD~1").pipe(catchAll(() => succeed$3(run.startParentHash))) : "";
192081
- const processDiff = [yield* git.diffRef(run.startParentHash).pipe(catchAll(() => succeed$3(""))), yield* git.diffHead().pipe(catchAll(() => succeed$3("")))].filter((d) => d.trim().length > 0).join("\n\n");
192138
+ const processDiff = [yield* git.diffRef(run.diffBase).pipe(catchAll(() => succeed$3(""))), yield* git.diffHead().pipe(catchAll(() => succeed$3("")))].filter((d) => d.trim().length > 0).join("\n\n");
192082
192139
  const lastDiff = run.trace.length > 0 ? yield* git.commitDiff(currentCommit).pipe(catchAll(() => succeed$3(""))) : "";
192083
192140
  const stepEntry = currentCost > 0 || currentModel !== void 0 ? [{
192084
192141
  cost: currentCost,
@@ -192086,7 +192143,7 @@ const buildTemplateContext = (git, read, state, actor, run, vars, edges, current
192086
192143
  }] : [];
192087
192144
  const allCostEntries = [...run.costEntries, ...stepEntry];
192088
192145
  return {
192089
- startCommit: run.startHash,
192146
+ startCommit: run.diffBase,
192090
192147
  currentCommit,
192091
192148
  previousCommit,
192092
192149
  state,
@@ -192289,10 +192346,12 @@ const closeReviewWindow = gen(function* () {
192289
192346
  * subcommand.
192290
192347
  *
192291
192348
  * The base is the most-recent in-process `reviewBase` commit
192292
- * (`reviewBaseHash`) or, absent any such state, the process start
192293
- * (`startParentHash`). When that resolves to the empty tree (a process with no
192294
- * prior commit) or to HEAD itself (an empty process), there is nothing to
192295
- * surface and the window stays closed.
192349
+ * (`reviewBaseHash`) or, absent any such state, the process's diff base
192350
+ * (`run.diffBase` — the process start, `startParentHash`, unless a
192351
+ * `gtd review <commitish>` entry commit overrode it via a `Gtd-Review-Base:`
192352
+ * trailer, see `computeProcessRun`). When that resolves to the empty tree (a
192353
+ * process with no prior commit) or to HEAD itself (an empty process), there
192354
+ * is nothing to surface and the window stays closed.
192296
192355
  *
192297
192356
  * Ordering is crash-safe: base ref → head ref → mixed reset → `.gtd/` index
192298
192357
  * pin → intent-to-add. A crash before the head-ref write leaves only a stale
@@ -192305,7 +192364,7 @@ const openReviewWindow = gen(function* () {
192305
192364
  if (!(yield* git.hasCommits())) return { opened: false };
192306
192365
  if (!isReviewWindowState(def, resolveState(def, yield* git.lastCommitSubject()))) return { opened: false };
192307
192366
  const run = yield* computeProcessRun(git, def);
192308
- const base = (yield* reviewBaseHash(git, def, run)) ?? run.startParentHash;
192367
+ const base = (yield* reviewBaseHash(git, def, run)) ?? run.diffBase;
192309
192368
  const headHash = yield* git.resolveRef("HEAD");
192310
192369
  if (base === EMPTY_TREE || base === headHash) return { opened: false };
192311
192370
  yield* git.updateRef(REVIEW_BASE_REF, base);
@@ -204046,8 +204105,9 @@ const resolveSteeringMode = (def, mode) => {
204046
204105
  const unknownModeMessage = (def, state, mode) => `state "${state}": mode "${mode}" is not defined by the active workflow (known modes: ${knownModes(def).join(", ")})`;
204047
204106
  /**
204048
204107
  * Run one rendered mode command through `bash -c` in `cwd`, capturing output
204049
- * instead of inheriting the terminal (unlike `gtd run`'s scripted actor, whose
204050
- * output IS the point): here the output is data — a validate command's findings
204108
+ * instead of inheriting the terminal (unlike a workflow script, which the driver
204109
+ * runs with inherited stdio because its output IS the point): here the output is
204110
+ * data — a validate command's findings
204051
204111
  * are reported to the agent that must fix them. A spawn failure (no `bash`, an
204052
204112
  * unreadable cwd) fails the Effect; a non-zero EXIT is a value, since that is
204053
204113
  * the mode's way of saying "invalid".
@@ -204130,8 +204190,7 @@ const formatAndValidateSteeringFile = (resolved, file, readContent, context, cwd
204130
204190
  });
204131
204191
  //#endregion
204132
204192
  //#region src/program.ts
204133
- const _require = createRequire(import.meta.url);
204134
- const GTD_VERSION = _require("../package.json").version;
204193
+ const GTD_VERSION = createRequire(import.meta.url)("../package.json").version;
204135
204194
  const HELP_TEXT = `Usage: gtd [command] [options]
204136
204195
 
204137
204196
  Commands:
@@ -204141,10 +204200,13 @@ Commands:
204141
204200
  --cost=<n> (optionally --model=<name>) to record the
204142
204201
  just-finished invocation's token cost and model on the
204143
204202
  turn commit (summed into it.processCost/processCostByModel)
204203
+ review <commitish>
204204
+ Start a NEW review process at the workflow's declared
204205
+ review-entry state (reviewEntry: true), reviewing
204206
+ <commitish>..HEAD — e.g. a colleague's PR branch. Requires
204207
+ a clean tree resting at the workflow's initial state
204144
204208
  next Print the resolved rest's rendered script/prompt/message
204145
204209
  (no mutation)
204146
- run Execute the resolved rest's emitted script, then step its
204147
- actor (the built-in script driver)
204148
204210
  status Print the resolved rest's state/actor and which declared
204149
204211
  pattern (if any) each pending change matches (no mutation)
204150
204212
  validate Format and validate the steering file the resolved rest
@@ -204247,7 +204309,7 @@ const runLspCommand = (argv, json) => gen(function* () {
204247
204309
  /**
204248
204310
  * Resolve HEAD's rest, the current process run, and the template context for
204249
204311
  * rendering that rest's OWN state/actor — the common prefix shared by `gtd
204250
- * next`, `gtd run`, and `gtd status` (each fetches `git` itself first, since
204312
+ * next` and `gtd status` (each fetches `git` itself first, since
204251
204313
  * `gtd status` also needs it for `pendingChanges`; `stepAsActor`'s squash
204252
204314
  * path renders a DIFFERENT state, so it builds its own context inline
204253
204315
  * instead of sharing this helper).
@@ -204269,8 +204331,7 @@ const resolveRestContext = (git) => gen(function* () {
204269
204331
  const formatStepRefusal = (invoker, refusal) => refusal.reason === "out-of-turn" ? `gtd step ${invoker}: out of turn — "${refusal.state}" awaits ${refusal.awaits}` : `gtd step ${invoker}: no declared pattern matches the pending changes at "${refusal.state}" — declared patterns: ${refusal.patterns.length > 0 ? refusal.patterns.join(", ") : "(none)"}`;
204270
204332
  /**
204271
204333
  * Authenticate `invoker` against the resolved rest and perform the one
204272
- * resulting transition (commit or squash), shared by `gtd step` and
204273
- * `gtd run` (which steps the script's own actor after executing it).
204334
+ * resulting transition (commit or squash) for `gtd step`.
204274
204335
  * Refusals fail the Effect with a formatted message; a no-op returns
204275
204336
  * `subject: null` rather than failing (exit zero, per the plan's "clean
204276
204337
  * no-op exits zero").
@@ -204307,7 +204368,7 @@ const stepAsActor = (invoker, cost, model) => gen(function* () {
204307
204368
  model: model ?? null
204308
204369
  };
204309
204370
  });
204310
- /** Renders `stepAsActor`'s result the same way for both `gtd step` and `gtd run`. */
204371
+ /** Renders `stepAsActor`'s result for `gtd step`. */
204311
204372
  const reportStepResult = (result, json, write) => {
204312
204373
  if (json) write(JSON.stringify({
204313
204374
  state: result.state,
@@ -204326,6 +204387,52 @@ const runStepCommand = (argv, json, write, cost, model) => gen(function* () {
204326
204387
  reportStepResult(result, json, write);
204327
204388
  });
204328
204389
  /**
204390
+ * `gtd review <commitish>`: start a brand NEW review process at the active
204391
+ * workflow's declared `reviewEntry: true` state (see
204392
+ * `PatternMachine.reviewEntryStateOf`), reviewing `<commitish>..HEAD` — e.g. a
204393
+ * colleague's PR branch pushed on top of a shared base, with no gtd process of
204394
+ * its own. Writes an ordinary EMPTY turn commit
204395
+ * (`gtd(human): <review-entry-state>`) carrying the resolved `<commitish>`'s
204396
+ * full hash as a `Gtd-Review-Base:` trailer (`withReviewBaseTrailer`) —
204397
+ * `computeProcessRun` reads that trailer back off this same commit (its
204398
+ * process's own oldest) to override the new process's diff base, so the
204399
+ * ENTIRE existing review flow (reviewing → await-review → feedback laps, the
204400
+ * `await-review` review checkout window) operates over `<commitish>..HEAD`
204401
+ * with zero duplicated logic — see `src/Edge.ts`/`src/ReviewWindow.ts`.
204402
+ *
204403
+ * Any failure below is a plain refusal: nothing is written.
204404
+ *
204405
+ * a. The machine must currently rest at the workflow's INITIAL state — a
204406
+ * plain non-gtd branch (the normal case) resolves there via the
204407
+ * inert-subject rule (STATES.md §5); a process already underway refuses.
204408
+ * b. The working tree must be clean.
204409
+ * c. The active workflow must declare a `reviewEntry: true` state.
204410
+ * d. `<commitish>` must resolve to a commit, be an ancestor of HEAD, and
204411
+ * differ from HEAD (nothing to review otherwise).
204412
+ */
204413
+ const runReviewCommand = (argv, json, write) => gen(function* () {
204414
+ const args = commandArgs(argv);
204415
+ if (args.length === 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd review: missing <commitish> argument"));
204416
+ if (args.length > 1) return yield* fail$2(/* @__PURE__ */ new Error(`gtd review: too many arguments — expected one <commitish>, got: ${args.join(", ")}`));
204417
+ const commitish = args[0];
204418
+ const git = yield* GitService;
204419
+ const rest = yield* resolveRest();
204420
+ 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 it before starting a review`));
204421
+ 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"));
204422
+ const entryState = reviewEntryStateOf(rest.def);
204423
+ if (entryState === void 0) return yield* fail$2(/* @__PURE__ */ new Error("gtd review: the active workflow declares no review entry state"));
204424
+ const resolvedBase = yield* git.resolveRef(commitish).pipe(mapError$1(() => /* @__PURE__ */ new Error(`gtd review: "${commitish}" does not resolve to a commit`)));
204425
+ if (resolvedBase === (yield* git.resolveRef("HEAD"))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd review: "${commitish}" is HEAD — nothing to review`));
204426
+ if (!(yield* git.isAncestor(resolvedBase, "HEAD"))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd review: "${commitish}" is not an ancestor of HEAD`));
204427
+ const subject = stateSubject("human", entryState);
204428
+ yield* git.commitAsIs(withReviewBaseTrailer(subject, resolvedBase));
204429
+ if (json) write(JSON.stringify({
204430
+ state: entryState,
204431
+ subject
204432
+ }) + "\n");
204433
+ else write(`committed: ${subject}\n`);
204434
+ });
204435
+ /**
204329
204436
  * The self-validation instruction gtd APPENDS to a `prompt` rest that declares
204330
204437
  * both `file:` and `mode:` — i.e. a state whose actor hands over a steering
204331
204438
  * file `gtd validate` formats and checks. Appended ONLY to plain `gtd next`
@@ -204450,32 +204557,6 @@ const runValidateCommand = (argv, json, write) => gen(function* () {
204450
204557
  }
204451
204558
  return yield* fail$2(/* @__PURE__ */ new Error(`${file} is not valid:\n${errors.map((e) => ` - ${e}`).join("\n")}`));
204452
204559
  });
204453
- /**
204454
- * `gtd run`: the built-in driver for a `script`-content rest. Renders the
204455
- * resolved rest exactly like `gtd next`, executes its content verbatim via
204456
- * `bash` (the ONLY place gtd spawns a subprocess), then steps that state's
204457
- * own actor to capture the outcome. Refuses when the resolved rest isn't a
204458
- * script (nothing to run).
204459
- */
204460
- const runRunCommand = (argv, json, write) => gen(function* () {
204461
- yield* rejectExtraArgs("run", argv);
204462
- const git = yield* GitService;
204463
- const { rest, context } = yield* resolveRestContext(git);
204464
- const rendered = yield* renderRest(rest, context);
204465
- if (rendered.kind !== "script") return yield* fail$2(/* @__PURE__ */ new Error(`gtd run: "${rest.state}" awaits a ${rendered.kind} from "${rest.actor}" — nothing scripted to run`));
204466
- yield* try_({
204467
- try: () => {
204468
- const { spawnSync } = _require("node:child_process");
204469
- spawnSync("bash", ["-c", rendered.content], {
204470
- cwd: process.cwd(),
204471
- stdio: "inherit"
204472
- });
204473
- },
204474
- catch: (e) => e instanceof Error ? e : new Error(String(e))
204475
- });
204476
- const result = yield* stepAsActor(rest.actor);
204477
- reportStepResult(result, json, write);
204478
- });
204479
204560
  /** Which declared `on` pattern (if any) each pending change matches — the pure computation `gtd status` reports (both plain and `--json`). */
204480
204561
  const computeStatusChanges = (onEdges, changes) => changes.map((change) => {
204481
204562
  const matchedRow = onEdges.find(([patternStr]) => {
@@ -204562,8 +204643,8 @@ const runMermaidCommand = (argv, json, write) => gen(function* () {
204562
204643
  });
204563
204644
  const KNOWN_SUBCOMMANDS = [
204564
204645
  "step",
204646
+ "review",
204565
204647
  "next",
204566
- "run",
204567
204648
  "status",
204568
204649
  "validate",
204569
204650
  "mermaid"
@@ -204613,8 +204694,8 @@ const assertRunningFromRepoRoot = (git, fs) => gen(function* () {
204613
204694
  const dispatchKnownSubcommand = (sub, argv, json, write, cost, model) => {
204614
204695
  switch (sub) {
204615
204696
  case "step": return runStepCommand(argv, json, write, cost, model);
204697
+ case "review": return runReviewCommand(argv, json, write);
204616
204698
  case "next": return runNextCommand(json, write);
204617
- case "run": return runRunCommand(argv, json, write);
204618
204699
  case "status": return runStatusCommand(argv, json, write);
204619
204700
  case "validate": return runValidateCommand(argv, json, write);
204620
204701
  case "mermaid": return runMermaidCommand(argv, json, write);
package/docs/cli.md CHANGED
@@ -10,6 +10,11 @@ Commands:
10
10
  --cost=<n> (optionally --model=<name>) to record the
11
11
  just-finished invocation's token cost and model on the
12
12
  turn commit (summed into it.processCost/processCostByModel)
13
+ review <commitish>
14
+ Start a NEW review process at the workflow's declared
15
+ review-entry state (reviewEntry: true), reviewing
16
+ <commitish>..HEAD — e.g. a colleague's PR branch. Requires
17
+ a clean tree resting at the workflow's initial state
13
18
  next Print the resolved rest's rendered script/prompt/message
14
19
  (no mutation)
15
20
  run Execute the resolved rest's emitted script, then step its
@@ -103,6 +108,49 @@ not passed):
103
108
  }
104
109
  ```
105
110
 
111
+ ## `gtd review <commitish> [--json]`
112
+
113
+ Starts a BRAND NEW review process at the active workflow's declared
114
+ `reviewEntry: true` state (see
115
+ [STATES.md §11](../STATES.md#11-the-review-checkout-window)), reviewing
116
+ `<commitish>..HEAD` — e.g. a colleague's PR branch pushed on top of a shared
117
+ base, with no gtd process of its own. Reuses the workflow's existing
118
+ review/feedback machinery unmodified; there is no separate review-entry command
119
+ surface to keep in sync.
120
+
121
+ Requires, in order (any failure is a plain refusal — exit non-zero, nothing
122
+ written):
123
+
124
+ - the machine currently resting at the workflow's **initial state** (a plain
125
+ non-gtd branch resolves there via the inert-subject rule — the normal case; a
126
+ process already underway refuses);
127
+ - a **clean working tree**;
128
+ - the active workflow declaring a **`reviewEntry: true`** state (otherwise:
129
+ `gtd review: the active workflow declares no review entry state`);
130
+ - `<commitish>` resolving to a commit, being an **ancestor of HEAD**, and
131
+ **differing from HEAD** (nothing to review otherwise).
132
+
133
+ On success, writes ONE empty commit, `gtd(human): <review-entry-state>`,
134
+ carrying the resolved commit's full hash as a `Gtd-Review-Base: <hash>` trailer
135
+ (mirroring how `gtd step --cost` writes its own `Gtd-Cost:` trailer). Everything
136
+ downstream that renders a diff — `it.processDiff`, and the review checkout
137
+ window opened by a downstream `reviewWindow: true` state — keys off the
138
+ process's diff base, and this trailer is exactly how that base gets re-pointed
139
+ at `<commitish>..HEAD` (see
140
+ [STATES.md §7](../STATES.md#7-retry)/[§11](../STATES.md#11-the-review-checkout-window)).
141
+
142
+ Plain-mode output is one line, same shape as `gtd step`:
143
+
144
+ ```
145
+ committed: gtd(human): reviewing
146
+ ```
147
+
148
+ `--json` emits `{state, subject}` — `state` is the entered review-entry state,
149
+ `subject` the bare commit subject (the trailer is not echoed).
150
+
151
+ Takes exactly one positional argument (`<commitish>`); missing or extra
152
+ arguments are usage errors.
153
+
106
154
  ## `gtd next [--json]`
107
155
 
108
156
  Pure emitter of the resolved rest's rendered content — it **never mutates** the
@@ -158,23 +206,15 @@ which are JSON-only. `--json` emits
158
206
  when the state has no `on` (a commit state); a per-edge `describe` is likewise
159
207
  omitted when that edge declares none.
160
208
 
161
- ## `gtd run`
162
-
163
- The built-in driver for a `script`-content rest — the **only** place gtd itself
164
- spawns a subprocess. Renders the resolved rest exactly like `gtd next`, executes
165
- its content verbatim via `bash -c` (foreground, inherited stdio, exit code
166
- deliberately ignored — a check script encodes its outcome in the tree, e.g.
167
- writing a findings file, never in its exit status), then runs `gtd step <actor>`
168
- for that state's own actor to capture the outcome in one command. Refuses (exit
169
- non-zero, no execution, no step) when the resolved rest isn't a script:
170
-
171
- ```
172
- gtd run: "<state>" awaits a <kind> from "<actor>" — nothing scripted to run
173
- ```
209
+ ## Running `script` rests (no `gtd` subcommand)
174
210
 
175
- Reports the same `{state, subject}` (or `committed: …` / `nothing to do at "…"`)
176
- as `gtd step` for the capturing step it performs. Takes no arguments — extra
177
- positional args are rejected.
211
+ There is no `gtd` subcommand that executes a workflow script — gtd never runs a
212
+ workflow script itself. When `gtd next` resolves to a `script`-content rest, the
213
+ **driver** (`bin/gtd-loop`, or any loop harness) executes the emitted `content`
214
+ verbatim via `bash -c` — foreground, inherited stdio, exit code deliberately
215
+ ignored (a check script encodes its outcome in the tree, e.g. writing a findings
216
+ file, never in its exit status) — then runs `gtd step <actor>` for that state's
217
+ own actor to capture the outcome. See [Driving the loop](loop.md).
178
218
 
179
219
  ## `gtd status [--json]`
180
220
 
@@ -370,8 +410,8 @@ the plain-text one.
370
410
  report, a log file — into the working tree, the tree never goes clean after a
371
411
  green run, and the check's `"C"` pattern never fires. Gitignore every path
372
412
  your scripts write before wiring gtd into a repo.
373
- - **Repository root invocation.** Every state subcommand (`step`/`next`/`run`/
374
- `status`/`mermaid`) must run from the git repository root — the workflow,
375
- pending changes, and process history are resolved against the process cwd.
376
- `--help`/`--version`, `format`, and `lsp` skip this guard entirely (and any
377
- git/`.gtdrc` dependency along with it).
413
+ - **Repository root invocation.** Every state subcommand (`step`/`review`/
414
+ `next`/`status`/`mermaid`) must run from the git repository root — the
415
+ workflow, pending changes, and process history are resolved against the
416
+ process cwd. `--help`/`--version`, `format`, and `lsp` skip this guard
417
+ entirely (and any git/`.gtdrc` dependency along with it).
@@ -331,9 +331,9 @@ The contract:
331
331
  not a malformed file): nothing is validated and nothing is committed.
332
332
  - **At least one of the two is required** in any single entry; the half it
333
333
  leaves out falls through to the layer beneath (below), or is a no-op.
334
- - Commands run via `bash -c` from the repository root — the only place besides a
335
- `script:` state's `gtd run` where gtd spawns a subprocess. A `.gtdrc` is
336
- project code: only add commands you would run yourself.
334
+ - Commands run via `bash -c` from the repository root — the only place gtd
335
+ itself spawns a subprocess (a `script:` state is run by the loop driver, not
336
+ by gtd). A `.gtdrc` is project code: only add commands you would run yourself.
337
337
 
338
338
  #### The layers, half by half
339
339
 
@@ -418,6 +418,43 @@ head is preserved under `refs/gtd/review-head` (the base under
418
418
  `refs/gtd/review-base`) for the window's lifetime. See
419
419
  [STATES.md §11](../STATES.md) for the full lifecycle.
420
420
 
421
+ ### `reviewEntry:` — the review entry point (`gtd review <commitish>`)
422
+
423
+ A state may separately declare `reviewEntry: true` (at most one across the whole
424
+ workflow; forbidden on a commit state and on the initial state). It marks the
425
+ state [`gtd review <commitish>`](cli.md#gtd-review-commitish---json) enters to
426
+ start a BRAND NEW process reviewing `<commitish>..HEAD` — e.g. a colleague's PR
427
+ branch with no gtd process of its own — reusing whatever `on`/
428
+ `reviewWindow`/`reviewBase` machinery that state and its downstream states
429
+ already declare, with zero duplicated logic.
430
+
431
+ ```yaml
432
+ workflow:
433
+ states:
434
+ # …
435
+ review:
436
+ actor: agent
437
+ reviewEntry: true # `gtd review <commitish>` starts a new process here
438
+ prompt: summarize <%~ it.processDiff %> for a human to check
439
+ on:
440
+ "* **": await-review
441
+ ```
442
+
443
+ `gtd review <commitish>` requires resting at the workflow's initial state with a
444
+ clean tree, and requires the active workflow to declare a `reviewEntry` state.
445
+ It resolves `<commitish>` (which must name an ancestor of HEAD other than HEAD
446
+ itself) and writes one empty `gtd(human): <review-entry-state>` turn commit
447
+ carrying the resolved hash as a `Gtd-Review-Base:` trailer — read back by
448
+ `computeProcessRun` to override the new process's diff base (`it.startCommit`
449
+ and `it.processDiff` then cover `<commitish>..HEAD`, and a downstream
450
+ `reviewWindow: true` state's checkout window opens over that same range). The
451
+ process's `retry`/squash trace boundary is untouched: the entry commit's own
452
+ parent is a non-workflow commit, so the ordinary boundary rule already stops
453
+ there. See [STATES.md §11](../STATES.md#11-the-review-checkout-window) for the
454
+ full mechanism.
455
+
456
+ The bundled default enables `reviewEntry: true` on `reviewing` itself.
457
+
421
458
  ### Template variables
422
459
 
423
460
  Every `script`/`prompt`/`message`/`commit`/`model`/`memory`/`file` template is
@@ -425,7 +462,7 @@ rendered as an Eta template (`it.<name>`) with:
425
462
 
426
463
  | Variable | Meaning |
427
464
  | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
428
- | `startCommit` | The hash the current process started from (before its first turn). |
465
+ | `startCommit` | The current process's diff base — normally the process start, but re-pointed to `<commitish>`'s hash for a process `gtd review <commitish>` started (see ["`reviewEntry:`"](#reviewentry--the-review-entry-point-gtd-review-commitish)). |
429
466
  | `currentCommit` | HEAD's hash at render time. |
430
467
  | `previousCommit` | The hash before the last transition (HEAD's parent, in-process). |
431
468
  | `state` | The state whose content is being rendered. |
@@ -518,8 +555,10 @@ vars:
518
555
  ```
519
556
 
520
557
  ```bash
521
- # highest precedence — beats both the workflow default and the .gtdrc value above
522
- GTD_VAR_testCommand="npm run test -- --bail" gtd run
558
+ # highest precedence — beats both the workflow default and the .gtdrc value above.
559
+ # The var is read at render time, so it shows up in the script `gtd next` prints
560
+ # (and in what the loop driver then executes).
561
+ GTD_VAR_testCommand="npm run test -- --bail" gtd next
523
562
  ```
524
563
 
525
564
  A template reads any of it as `it.vars.<name>`:
@@ -23,7 +23,10 @@ Compared to the simplified bundled default, this machine adds:
23
23
  `checking`/`fixing` loop, until the queue empties.
24
24
  - **Agent-prepared review** — `reviewing` has the agent write `.gtd/REVIEW.md`
25
25
  summarizing the full cycle diff for a human to check before `await-review`
26
- resolves an approval or feedback.
26
+ resolves an approval or feedback. `reviewing` also declares
27
+ `reviewEntry: true`, so `gtd review <commitish>` can start a review process
28
+ here directly over `<commitish>..HEAD` (e.g. a colleague's PR branch) — see
29
+ [STATES.md §11](../../STATES.md#11-the-review-checkout-window).
27
30
  - **A squash finale** — approval at `await-review` moves to `squashing` (the
28
31
  agent authors `.gtd/COMMIT_MSG.md`) and then `done`, a `commit:` state that
29
32
  squashes the whole cycle into one commit. The simplified bundled default drops
@@ -333,6 +336,10 @@ workflow:
333
336
  file: <%= it.vars.reviewFile %>
334
337
  mode: review
335
338
  model: smart
339
+ # A second way in: `gtd review <commitish>` (clean tree, resting at the
340
+ # initial state) enters here directly over <commitish>..HEAD — see
341
+ # STATES.md §11.
342
+ reviewEntry: true
336
343
  prompt: |
337
344
  You are an autonomous coding agent. `.gtd/` holds this workflow's own
338
345
  state — never create, edit, or delete anything under `.gtd/` except
package/docs/loop.md CHANGED
@@ -11,21 +11,22 @@ gtd (v3, the pattern machine) exposes three commands a loop driver combines:
11
11
  [Configuration: Token cost](configuration.md#token-cost)).
12
12
  - **`gtd next`** — print the resolved rest's rendered script/prompt/message,
13
13
  without mutating anything.
14
- - **`gtd run`** — execute the resolved rest's emitted script (only for a
15
- `script`-content rest), then step that state's own actor to capture the
16
- outcome. The only place gtd spawns a subprocess.
14
+
15
+ gtd never executes a workflow script itself: at a `script`-content rest the
16
+ DRIVER runs the emitted `content` and then steps that state's own actor.
17
17
 
18
18
  A loop is a simple cycle:
19
19
 
20
20
  1. Run `gtd next --json` — see [`cli.md`](cli.md#gtd-next---json) for the exact
21
21
  `{state, actor, kind, content, model?, memory?}` field shape. `kind` is the
22
- dispatch key: `"message"` → halt (a human rest); `"script"` → run `gtd run`;
23
- `"prompt"` → feed `content` to the agent, then `gtd step <actor>` yourself
24
- once it's done acting. The optional `model` is an opaque string the workflow
25
- author chose, which you map onto your own harness's model selection if you
26
- use one.
27
- 2. Repeat until a `"message"` rest halts the loop, or a zero-commit `gtd run` at
28
- idle settles it (the green terminal signal).
22
+ dispatch key: `"message"` → halt (a human rest); `"script"` → execute
23
+ `content` verbatim (ignoring its exit code), then `gtd step <actor>` to
24
+ capture the outcome; `"prompt"` → feed `content` to the agent, then
25
+ `gtd step <actor>` yourself once it's done acting. The optional `model` is an
26
+ opaque string the workflow author chose, which you map onto your own
27
+ harness's model selection if you use one.
28
+ 2. Repeat until a `"message"` rest halts the loop, or a zero-commit script step
29
+ at idle settles it (the green terminal signal).
29
30
 
30
31
  A human acts by editing files (e.g. writing/editing `.gtd/TODO.md`, fixing code)
31
32
  and then running `gtd step human` to capture the edit as their turn.
@@ -75,7 +76,8 @@ while true; do
75
76
 
76
77
  if [[ "$kind" == "script" ]]; then
77
78
  head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
78
- gtd run
79
+ bash -c "$content" || true # run the emitted script; exit code ignored
80
+ gtd step "$actor" >/dev/null # capture whatever it left in the tree
79
81
  head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
80
82
  if [[ "$head_before" == "$head_after" ]]; then
81
83
  echo "--- Settled ($state: check passed, nothing to do) ---"
package/docs/upgrading.md CHANGED
@@ -71,9 +71,16 @@ upgrading.
71
71
  (see
72
72
  [docs/design/steering-file-validation-command.md](design/steering-file-validation-command.md)
73
73
  and [STATES.md §12](../STATES.md)).
74
- - **`gtd review <target>`.** Gone; the v3 command surface is `step` / `next` /
75
- `run` / `status` / `validate` / `mermaid` / `lsp` (see
76
- [CLI reference](cli.md)).
74
+ - **`gtd review <target>`.** Gone in the initial v3 rewrite (the command surface
75
+ was `step` / `next` / `run` / `status` / `validate` / `mermaid` / `lsp`), then
76
+ RETURNED in this version with different, narrower semantics: it no longer
77
+ inspects an arbitrary target the way v2 did — it starts a brand NEW review
78
+ process at a workflow-declared `reviewEntry: true` state, reviewing
79
+ `<target>..HEAD` (e.g. a colleague's PR branch with no gtd process of its own)
80
+ by writing one empty entry commit with a `Gtd-Review-Base:` trailer and
81
+ reusing the workflow's existing review/feedback machinery unmodified — see
82
+ [STATES.md §11](../STATES.md#11-the-review-checkout-window) and
83
+ [CLI reference](cli.md#gtd-review-commitish---json).
77
84
  - **`gtd format <file>`, and the bundled prettier with it.** gtd no longer
78
85
  formats anything on its own: a steering-file mode declares its own `format:`
79
86
  SHELL COMMAND, so a project brings whatever formatter it already uses. Adding
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "4.0.0",
3
+ "version": "5.1.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
package/schema.json CHANGED
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "script": {
53
53
  "type": "string",
54
- "description": "Content kind: a shell script (Eta template). `gtd run` executes it verbatim via bash, then steps this state's actor."
54
+ "description": "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
55
55
  },
56
56
  "prompt": {
57
57
  "type": "string",
@@ -136,6 +136,10 @@
136
136
  "reviewBase": {
137
137
  "type": "boolean",
138
138
  "description": "Marks the state whose most-recent in-process commit anchors the review window's diff base; absent any, the base is the process start. Forbidden on a commit state."
139
+ },
140
+ "reviewEntry": {
141
+ "type": "boolean",
142
+ "description": "Marks the (at most one) state `gtd review <commitish>` enters to start a brand new process reviewing <commitish>..HEAD. Forbidden on a commit state and on the initial state."
139
143
  }
140
144
  }
141
145
  }