@pmelab/gtd 5.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 +13 -8
- package/dist/gtd.bundle.mjs +128 -16
- package/docs/cli.md +53 -5
- package/docs/configuration.md +38 -1
- package/docs/examples/advanced-workflow.md +8 -1
- package/docs/upgrading.md +10 -3
- package/package.json +1 -1
- package/schema.json +4 -0
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ schema stub on first run (see [Configuration](docs/configuration.md#auto-init)).
|
|
|
56
56
|
gtd is a small **pattern machine**: named states, each awaiting one actor and
|
|
57
57
|
carrying one piece of content (a script, a prompt, a message, or a squash commit
|
|
58
58
|
template), with an ordered set of change-patterns routing to the next state.
|
|
59
|
-
|
|
59
|
+
Four commands drive it:
|
|
60
60
|
|
|
61
61
|
- **`gtd step <actor>`** — authenticate as `<actor>` and perform the one
|
|
62
62
|
transition the pending changes match.
|
|
@@ -64,6 +64,9 @@ Three commands drive it:
|
|
|
64
64
|
should do, without mutating anything.
|
|
65
65
|
- **`gtd status`** — a dry-run report of the resolved state and which pattern
|
|
66
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.
|
|
67
70
|
|
|
68
71
|
The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
|
|
69
72
|
`"message"` means it's a human's move (stop and hand off); `"script"` means the
|
|
@@ -78,13 +81,15 @@ 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.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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/dist/gtd.bundle.mjs
CHANGED
|
@@ -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;
|
|
189531
|
-
*
|
|
189532
|
-
*
|
|
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 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 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"), ".");
|
|
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;
|
|
@@ -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.
|
|
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.
|
|
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
|
|
192293
|
-
* (`
|
|
192294
|
-
*
|
|
192295
|
-
*
|
|
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.
|
|
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);
|
|
@@ -204141,6 +204200,11 @@ 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
204210
|
status Print the resolved rest's state/actor and which declared
|
|
@@ -204323,6 +204387,52 @@ const runStepCommand = (argv, json, write, cost, model) => gen(function* () {
|
|
|
204323
204387
|
reportStepResult(result, json, write);
|
|
204324
204388
|
});
|
|
204325
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
|
+
/**
|
|
204326
204436
|
* The self-validation instruction gtd APPENDS to a `prompt` rest that declares
|
|
204327
204437
|
* both `file:` and `mode:` — i.e. a state whose actor hands over a steering
|
|
204328
204438
|
* file `gtd validate` formats and checks. Appended ONLY to plain `gtd next`
|
|
@@ -204533,6 +204643,7 @@ const runMermaidCommand = (argv, json, write) => gen(function* () {
|
|
|
204533
204643
|
});
|
|
204534
204644
|
const KNOWN_SUBCOMMANDS = [
|
|
204535
204645
|
"step",
|
|
204646
|
+
"review",
|
|
204536
204647
|
"next",
|
|
204537
204648
|
"status",
|
|
204538
204649
|
"validate",
|
|
@@ -204583,6 +204694,7 @@ const assertRunningFromRepoRoot = (git, fs) => gen(function* () {
|
|
|
204583
204694
|
const dispatchKnownSubcommand = (sub, argv, json, write, cost, model) => {
|
|
204584
204695
|
switch (sub) {
|
|
204585
204696
|
case "step": return runStepCommand(argv, json, write, cost, model);
|
|
204697
|
+
case "review": return runReviewCommand(argv, json, write);
|
|
204586
204698
|
case "next": return runNextCommand(json, write);
|
|
204587
204699
|
case "status": return runStatusCommand(argv, json, write);
|
|
204588
204700
|
case "validate": return runValidateCommand(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
|
|
@@ -362,8 +410,8 @@ the plain-text one.
|
|
|
362
410
|
report, a log file — into the working tree, the tree never goes clean after a
|
|
363
411
|
green run, and the check's `"C"` pattern never fires. Gitignore every path
|
|
364
412
|
your scripts write before wiring gtd into a repo.
|
|
365
|
-
- **Repository root invocation.** Every state subcommand (`step`/`
|
|
366
|
-
`status`/`mermaid`) must run from the git repository root — the
|
|
367
|
-
pending changes, and process history are resolved against the
|
|
368
|
-
`--help`/`--version`, `format`, and `lsp` skip this guard
|
|
369
|
-
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).
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
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. |
|
|
@@ -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/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
|
|
75
|
-
`run` / `status` / `validate` / `mermaid` / `lsp`
|
|
76
|
-
|
|
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
package/schema.json
CHANGED
|
@@ -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
|
}
|