@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 +18 -13
- package/bin/gtd-loop +10 -4
- package/dist/gtd.bundle.mjs +136 -55
- package/docs/cli.md +61 -21
- package/docs/configuration.md +45 -6
- package/docs/examples/advanced-workflow.md +8 -1
- package/docs/loop.md +13 -11
- package/docs/upgrading.md +10 -3
- package/package.json +1 -1
- package/schema.json +5 -1
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
|
-
|
|
73
|
-
`gtd step <actor>` once it's done.
|
|
74
|
-
|
|
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.
|
|
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/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) ->
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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
|
-
|
|
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) ---"
|
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 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).
|
|
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.
|
|
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);
|
|
@@ -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
|
|
204050
|
-
* output IS the point): here the output is
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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`/`
|
|
374
|
-
`status`/`mermaid`) must run from the git repository root — the
|
|
375
|
-
pending changes, and process history are resolved against the
|
|
376
|
-
`--help`/`--version`, `format`, and `lsp` skip this guard
|
|
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).
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
335
|
-
`script:` state
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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"` →
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"script": {
|
|
53
53
|
"type": "string",
|
|
54
|
-
"description": "Content kind: a shell script (Eta template).
|
|
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
|
}
|