@pmelab/gtd 1.9.2 → 2.0.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 +659 -757
- package/dist/gtd.bundle.mjs +3288 -2924
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,33 +4,69 @@
|
|
|
4
4
|
> might be terrible, I don't even know 🤷♂️ But otherwise I wouldn't have built it
|
|
5
5
|
> in the first place. Now I have something that actually helps me.
|
|
6
6
|
|
|
7
|
-
A git-aware CLI that
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
A git-aware CLI that drives a turn-taking loop between a human and an autonomous
|
|
8
|
+
coding agent: capture an idea, grill it into a plan, decompose it into work
|
|
9
|
+
packages, execute with parallel subagents, test, agentically review each
|
|
10
|
+
package, and finally walk a human through a review — squashing the whole cycle
|
|
11
|
+
into one conventional-commits commit at the end.
|
|
11
12
|
|
|
12
13
|
Internally, gtd is a **pure fold** over git history. The decision core
|
|
13
14
|
(`src/Machine.ts`) is a single IO-free function, `resolve(events)` — **no
|
|
14
15
|
xstate, no actor, no Effect**. The Effect "edge" (`src/Events.ts`) does all the
|
|
15
16
|
git/filesystem IO: it reads the **first-parent** commit subjects since the
|
|
16
17
|
merge-base with the default branch (whole-history fallback when there is no
|
|
17
|
-
default branch, when HEAD equals the merge-base, or when there is no merge-base
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
default branch, when HEAD equals the merge-base, or when there is no merge-base)
|
|
19
|
+
plus the working tree, turns them into a `COMMIT[]` + single terminal `RESOLVE`
|
|
20
|
+
event stream, and folds them through the machine. The fold lands on exactly
|
|
21
|
+
**one** of 16 states, plus which actor (human or agent) is awaited there. A
|
|
22
|
+
single call resolves to a single state.
|
|
23
|
+
|
|
24
|
+
Steering is entirely **machine-authored commit subjects** — there are no marker
|
|
25
|
+
files, sentinels, or auto-advance tails to parse. A turn commit looks like
|
|
26
|
+
`gtd(human): grilling` or `gtd(agent): building`; a routing commit (bookkeeping
|
|
27
|
+
the machine performs itself between turns) looks like `gtd: tests green`.
|
|
28
|
+
`src/Subjects.ts` is the closed grammar both the machine and the edge read.
|
|
29
|
+
|
|
30
|
+
All workflow state lives under **`.gtd/`**: the plan (`.gtd/TODO.md`), work
|
|
31
|
+
packages (`.gtd/01-…/`), review records (`.gtd/REVIEW.md`, `.gtd/FEEDBACK.md`),
|
|
32
|
+
and loop bookkeeping (`.gtd/ERRORS.md`, `.gtd/HEALTH.md`, `.gtd/SQUASH_MSG.md`).
|
|
33
|
+
One rule follows for every agent in the loop: **never touch `.gtd/`** except the
|
|
34
|
+
single file a prompt explicitly grants. A `TODO.md` or `REVIEW.md` at the
|
|
35
|
+
repository root is the project's own file — gtd never reads, consumes, or
|
|
36
|
+
deletes it. (Corollary: don't gitignore `.gtd/` — the workflow commits its state
|
|
37
|
+
through it.)
|
|
38
|
+
|
|
39
|
+
## Quick start: the two-beat loop
|
|
40
|
+
|
|
41
|
+
gtd splits what used to be one mutating command into three:
|
|
42
|
+
|
|
43
|
+
- **`gtd step`** — advance the workflow as the **human** actor, to fixpoint.
|
|
44
|
+
- **`gtd step-agent`** — advance the workflow as the **agent** actor, to
|
|
45
|
+
fixpoint.
|
|
46
|
+
- **`gtd next`** — print the prompt for whichever actor is currently awaited,
|
|
47
|
+
without mutating anything.
|
|
48
|
+
|
|
49
|
+
An agent loop is a two-beat protocol repeated forever:
|
|
50
|
+
|
|
51
|
+
1. Run `gtd step-agent` to advance any agent-owned bookkeeping to a fixpoint.
|
|
52
|
+
2. Run `gtd next --json` and read the `actor` field. If it is `"human"`,
|
|
53
|
+
**halt** — the human owns the next move, and the agent's job is done for this
|
|
54
|
+
turn. If it is `"agent"`, feed `prompt` (when non-null) to the agent, let it
|
|
55
|
+
act, then go back to step 1; at a pending checkpoint (`prompt` is null) go
|
|
56
|
+
straight back to step 1.
|
|
57
|
+
|
|
58
|
+
A human acts by editing files (answering questions in `.gtd/TODO.md`, annotating
|
|
59
|
+
`.gtd/REVIEW.md`, fixing code) and then running `gtd step` to capture the edit
|
|
60
|
+
as their turn and hand control back to the agent side of the loop.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gtd step-agent # advance the machine's own bookkeeping
|
|
64
|
+
gtd next --json # ask who's up and what they should do
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See [The reference loop driver](#the-reference-loop-driver) for a full script
|
|
68
|
+
implementing this protocol, and [`skills/loop/SKILL.md`](skills/loop/SKILL.md)
|
|
69
|
+
for the agent-facing instructions that follow the same pinned contract.
|
|
34
70
|
|
|
35
71
|
## Installation
|
|
36
72
|
|
|
@@ -44,493 +80,515 @@ Or run without installing:
|
|
|
44
80
|
npx @pmelab/gtd
|
|
45
81
|
```
|
|
46
82
|
|
|
47
|
-
No config file, no setup subcommand.
|
|
48
|
-
|
|
49
|
-
## Usage
|
|
50
|
-
|
|
51
|
-
Run `gtd` from your repository's working directory — it prints the next prompt
|
|
52
|
-
to stdout. It takes **no ref argument** — the review base is always
|
|
53
|
-
auto-computed. The one exception is `gtd review <target>`: an explicit,
|
|
54
|
-
on-demand human review against a chosen base (see
|
|
55
|
-
[Review subcommand](#review-subcommand) below).
|
|
83
|
+
No config file, no setup subcommand — `gtd` auto-initializes a `.gtdrc.json`
|
|
84
|
+
schema stub on first run (see [Auto-init](#auto-init)).
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
now runs a **health check** instead of stopping immediately — `gtd review` is
|
|
59
|
-
the only way to start an ad-hoc review when the automatic review base yields no
|
|
60
|
-
diff.
|
|
86
|
+
## Command reference
|
|
61
87
|
|
|
62
|
-
|
|
88
|
+
```
|
|
89
|
+
Usage: gtd [command] [options]
|
|
63
90
|
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
Commands:
|
|
92
|
+
step Advance the workflow as the human actor (to fixpoint)
|
|
93
|
+
step-agent Advance the workflow as the agent actor (to fixpoint)
|
|
94
|
+
next Print the prompt for whichever actor is awaited (no mutation)
|
|
95
|
+
status Predict the next commit and state from the working tree (no mutation)
|
|
96
|
+
review <target> Anchor an ad-hoc human review against a git ref or branch
|
|
97
|
+
format <file> Format a markdown file in place
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
Options:
|
|
100
|
+
--json Output structured JSON instead of plain text
|
|
101
|
+
--version, -v Print version and exit
|
|
102
|
+
--help, -h Print this help and exit
|
|
69
103
|
```
|
|
70
104
|
|
|
71
|
-
`--
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
`--version` (`-v`) and `--help` (`-h`) short-circuit before any git or
|
|
106
|
+
repository-state work — they run outside a repo and in any repo state. Bare
|
|
107
|
+
`gtd` (no subcommand) is a usage error: it prints the help text and exits 1
|
|
108
|
+
without touching the repository. Every other command must be run from the
|
|
109
|
+
**repository root** — gtd derives steering files, diffs, and pathspecs relative
|
|
110
|
+
to cwd, so it refuses with a clear error if invoked from a subdirectory.
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
`--json` is the only long option. Any other `--` option (including a typo like
|
|
113
|
+
`--jsn`) is rejected with a usage error rather than silently ignored, so a
|
|
114
|
+
mistyped flag can never degrade a JSON caller to plain-text mode.
|
|
76
115
|
|
|
77
|
-
|
|
116
|
+
### `gtd step` / `gtd step-agent`
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
Both drive the **same fixpoint loop** — gather → resolve → perform the returned
|
|
119
|
+
edge action → repeat — differing only in which actor's turn they are allowed to
|
|
120
|
+
capture:
|
|
82
121
|
|
|
83
|
-
- **`
|
|
84
|
-
|
|
85
|
-
- **`autoAdvance`** — the same boolean that selects the loop-tail in plain mode.
|
|
86
|
-
`true` means the workflow advances automatically after the agent acts; `false`
|
|
87
|
-
means a STOP state was reached and human input is expected.
|
|
88
|
-
- **`prompt`** — the full markdown prompt, but with **both loop-control tails
|
|
89
|
-
omitted**. In their place, the prompt ends with:
|
|
90
|
-
`Complete the steps above, then end your turn — the harness decides what happens next.`
|
|
91
|
-
The caller is responsible for reading `autoAdvance` and deciding whether to
|
|
92
|
-
run another cycle.
|
|
122
|
+
- **`gtd step`** captures the **human** turn at whichever gate is awaiting one.
|
|
123
|
+
- **`gtd step-agent`** captures the **agent** turn.
|
|
93
124
|
|
|
94
|
-
|
|
125
|
+
**Fixpoint advance.** A single invocation may author several commits: it authors
|
|
126
|
+
the awaited actor's turn commit, then keeps performing any further mid-chain
|
|
127
|
+
routing (a test run, a routing commit, a package close, …) until it reaches a
|
|
128
|
+
rest where a prompt would be shown, or a fixpoint where nothing changed.
|
|
129
|
+
`gtd step`/`gtd step-agent` never print a prompt themselves — that's
|
|
130
|
+
`gtd next`'s job.
|
|
95
131
|
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
**Idempotence.** Re-running the same command again once the tree is settled at a
|
|
133
|
+
rest authors **zero** new commits. It exits 0 while the rest still awaits that
|
|
134
|
+
command's actor (an inert empty agent turn, the idle health check); once the
|
|
135
|
+
rest awaits the _other_ actor, the re-run is an out-of-turn refusal — still zero
|
|
136
|
+
commits, but non-zero exit.
|
|
98
137
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
138
|
+
**Out-of-turn refusal.** Human and agent turns are strictly separated: the wrong
|
|
139
|
+
mutator always errors, at every state, on clean and dirty trees alike.
|
|
140
|
+
`gtd step-agent` while a human turn is awaited refuses with
|
|
141
|
+
`"<state> awaits a human turn — run \`gtd step\`"`; `gtd
|
|
142
|
+
step`while an agent turn is awaited refuses with`"<state> awaits an agent turn —
|
|
143
|
+
run \`gtd
|
|
144
|
+
step-agent\`"`— exit non-zero, zero commits either way. Human edits made while the agent is awaited (e.g. amendment notes in`.gtd/`package files after the`gtd:
|
|
145
|
+
planning` commit lands) stay pending in the working tree and ride along as input
|
|
146
|
+
to the agent's next captured turn; left unamended, the build proceeds.
|
|
102
147
|
|
|
103
|
-
|
|
148
|
+
**Red-test fixpoints exit 0.** A red test run below the fix-attempt cap (or the
|
|
149
|
+
health-fix cap) still writes its findings and commits — it is a normal,
|
|
150
|
+
successful step of the loop, not a failure of the `step`/`step-agent`
|
|
151
|
+
invocation. `step`/`step-agent` only exit non-zero for a genuine refusal or an
|
|
152
|
+
operational error (bad config, missing test binary, corrupted state).
|
|
104
153
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
set -euo pipefail
|
|
154
|
+
**Output.** Plain mode prints one `committed: <subject>` line per commit this
|
|
155
|
+
invocation authored (oldest→newest), then a final `state: <state>` line:
|
|
108
156
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
jq -e .autoAdvance <<<"$out" >/dev/null || break
|
|
114
|
-
done
|
|
157
|
+
```
|
|
158
|
+
committed: gtd(human): grilling
|
|
159
|
+
committed: gtd: grilled
|
|
160
|
+
state: grilled
|
|
115
161
|
```
|
|
116
162
|
|
|
117
|
-
|
|
163
|
+
`--json` emits `{state, actions, commits}` instead (see
|
|
164
|
+
[JSON schemas](#json-schemas)).
|
|
118
165
|
|
|
119
|
-
|
|
166
|
+
### `gtd next`
|
|
167
|
+
|
|
168
|
+
Pure prompt emitter — it **never mutates** the repository. It reports whichever
|
|
169
|
+
actor is currently awaited and, if the tree is at a genuine rest, the full
|
|
170
|
+
prompt for that actor.
|
|
171
|
+
|
|
172
|
+
**Purity.** No commits, no file writes, no test runs — `gtd next` only gathers
|
|
173
|
+
and resolves.
|
|
174
|
+
|
|
175
|
+
**Dirty-tree refusal.** If the working tree has pending changes outside the
|
|
176
|
+
steering-file set, `gtd next` refuses rather than guess at a prompt for a state
|
|
177
|
+
that hasn't been captured yet:
|
|
120
178
|
|
|
121
|
-
```json
|
|
122
|
-
{ "state": "error", "autoAdvance": false, "prompt": "<message>" }
|
|
123
179
|
```
|
|
180
|
+
gtd next: working tree is dirty — run `gtd status` to inspect it, then advance with `gtd step` or `gtd step-agent` (whichever actor is awaited)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Pending.** If HEAD is mid-chain — bookkeeping the next `step`/`step-agent`
|
|
184
|
+
invocation would perform before reaching a rest — `gtd next` reports
|
|
185
|
+
`pending: true` with no prompt. Mid-chain bookkeeping is invoker-agnostic, so
|
|
186
|
+
either mutator resumes it; the report names the actor whose chain it is. In
|
|
187
|
+
plain mode an agent-driven checkpoint prints `"mid-chain checkpoint — run \`gtd
|
|
188
|
+
step-agent\` to continue, then run \`gtd next\`
|
|
189
|
+
again"`, a human-driven one prints `"mid-chain checkpoint — run \`gtd step\` to
|
|
190
|
+
continue"`.
|
|
124
191
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- **TODO.md** — the current plan, under development during grilling.
|
|
134
|
-
- **REVIEW.md** — a guided human review spanning a commit diff. Format:
|
|
135
|
-
- `# Review: <short-hash>` heading + `<!-- base: <full-hash> -->` marker
|
|
136
|
-
identifying the review base commit
|
|
137
|
-
- Per-hunk `- [ ]` checkboxes: ticking them (`- [ ]` → `- [x]`) is the
|
|
138
|
-
**approval signal** — checkbox-only edits route to Done; _unchecked_ boxes
|
|
139
|
-
never gate the workflow
|
|
140
|
-
- Open questions at the top, resolved/addressed items at the bottom
|
|
141
|
-
(consistent with TODO.md grilling convention)
|
|
142
|
-
- **FEEDBACK.md** — test-failure output, **or** agentic-review findings, to be
|
|
143
|
-
fixed. An **empty** FEEDBACK.md from a clean agentic review signals
|
|
144
|
-
**approval** (→ Close package).
|
|
145
|
-
- **ERRORS.md** — the escalation gate: persistent test-failure output that stops
|
|
146
|
-
the loop for a human (written instead of FEEDBACK.md once the fix-attempt cap
|
|
147
|
-
is hit; never auto-consumed).
|
|
148
|
-
- **HEALTH.md** — idle health-check failure output: written when `testCommand`
|
|
149
|
-
fails on a bare idle tree (no `.gtd`, no REVIEW.md, no FEEDBACK.md). Carries
|
|
150
|
-
the failure while the dedicated health-fix loop repairs it. Analogous to
|
|
151
|
-
FEEDBACK.md but lives on the default-branch idle path rather than inside a
|
|
152
|
-
build process. Never written while any other steering file is present.
|
|
153
|
-
- **.gtd/** — ordered work packages (one numbered directory each) of
|
|
154
|
-
parallelizable subtasks.
|
|
155
|
-
|
|
156
|
-
Steering files are **authoritative**: while any exist, `gtd` resumes that
|
|
157
|
-
workflow regardless of the last commit (even a non-gtd one). They are **never
|
|
158
|
-
garbage-collected automatically** — a stale steering file from an abandoned
|
|
159
|
-
branch is resumed exactly like a live one, so you must `rm` files from a
|
|
160
|
-
workflow you have abandoned.
|
|
161
|
-
|
|
162
|
-
"**Code changes**" below means pending working-tree changes (tracked or
|
|
163
|
-
untracked, respecting `.gitignore`) **outside** the steering set. Changes to
|
|
164
|
-
steering files are detected separately.
|
|
165
|
-
|
|
166
|
-
## Detection model
|
|
167
|
-
|
|
168
|
-
Every run derives the state in **three layers**:
|
|
169
|
-
|
|
170
|
-
1. **Transport pre-pass** — if HEAD is `gtd: transport`, short-circuit to the
|
|
171
|
-
Transport state (mixed-reset) before anything else is considered.
|
|
172
|
-
2. **Steering-file precedence** — the presence of `ERRORS.md` / `FEEDBACK.md` /
|
|
173
|
-
`HEALTH.md` / `.gtd/` / `REVIEW.md` drives the decision, authoritative
|
|
174
|
-
regardless of HEAD.
|
|
175
|
-
3. **HEAD bucket** — with no steering files in play, the last-commit bucket plus
|
|
176
|
-
working-tree cleanliness selects New Feature / Grilling / Clean / Health
|
|
177
|
-
check / Idle.
|
|
178
|
-
|
|
179
|
-
Within layers 2 and 3 the HEAD subject further disambiguates states the
|
|
180
|
-
filesystem alone cannot separate (e.g. inside the `.gtd/` lifecycle, HEAD
|
|
181
|
-
`gtd: planning` vs `gtd: building` vs `gtd: package done`).
|
|
182
|
-
|
|
183
|
-
### Commit taxonomy
|
|
184
|
-
|
|
185
|
-
`gtd` writes a single **flat** `gtd: <phase>` subject for every workflow commit.
|
|
186
|
-
The complete set:
|
|
187
|
-
|
|
188
|
-
`gtd: new task` · `gtd: grilling` · `gtd: grilled` · `gtd: planning` ·
|
|
189
|
-
`gtd: building` · `gtd: errors` · `gtd: feedback` · `gtd: fixing` ·
|
|
190
|
-
`gtd: package done` · `gtd: awaiting review` · `gtd: done` · `gtd: health-check`
|
|
191
|
-
· `gtd: health-fix` · `gtd: reviewing` — plus the hand-made `gtd: transport`
|
|
192
|
-
(see below).
|
|
193
|
-
|
|
194
|
-
The last commit subject is bucketed two ways:
|
|
195
|
-
|
|
196
|
-
- **Boundary** — a non-`gtd:` commit, or exactly `gtd: done`. Marks a cold
|
|
197
|
-
start: no workflow in progress.
|
|
198
|
-
- **Mid-phase** — any other `gtd: <phase>` subject. Identifies the exact phase
|
|
199
|
-
of an in-progress workflow.
|
|
200
|
-
|
|
201
|
-
### Precedence ladder (first match wins)
|
|
202
|
-
|
|
203
|
-
0. **HEAD `gtd: transport`** → Transport.
|
|
204
|
-
1. **ERRORS.md present** → Escalate (human gate; STOP).
|
|
205
|
-
2. **FEEDBACK.md present** → non-empty → Fixing; **empty** (clean agentic review
|
|
206
|
-
= approval) → Close package.
|
|
207
|
-
3. **HEALTH.md present** → Health Fixing (idle health-fix loop; no `.gtd`,
|
|
208
|
-
REVIEW.md, or FEEDBACK.md).
|
|
209
|
-
4. **.gtd present** → build lifecycle, routed by tree + HEAD:
|
|
210
|
-
- `.gtd` modified (package files added/edited) → Planning
|
|
211
|
-
- code changes present → Testing
|
|
212
|
-
- clean tree + HEAD `gtd: fixing` (no-op fixer) → Testing (re-test)
|
|
213
|
-
- else clean, by HEAD: `gtd: planning` / `gtd: package done` → Building;
|
|
214
|
-
`gtd: building` → Agentic Review (or Close package, if force-approved)
|
|
215
|
-
5. **REVIEW.md present** → review lifecycle, routed by committed-ness + tree:
|
|
216
|
-
committed + clean → Done; committed + checkbox-only edits (only `[ ]`↔`[x]`
|
|
217
|
-
flips in REVIEW.md) → Done; committed + non-checkbox pending edits → Accept
|
|
218
|
-
Review; uncommitted → Await Review (commits REVIEW.md and auto-advances to
|
|
219
|
-
Done). 5a. **HEAD `gtd: done` + `squash` enabled + squash base present + no
|
|
220
|
-
unrelated code dirty** (a lone untracked `SQUASH_MSG.md` is allowed) →
|
|
221
|
-
Squashing; unrelated code dirty → New Feature. 8a. **Green health check + ≥1
|
|
222
|
-
`gtd: health-fix` + `squash` enabled** → Squashing (same agent-authored
|
|
223
|
-
conventional-commits path as the feature-cycle squash — no hardcoded
|
|
224
|
-
placeholder).
|
|
225
|
-
6. **Boundary HEAD + pending changes** (and no `.gtd`/REVIEW/FEEDBACK), or HEAD
|
|
226
|
-
`gtd: new task` + clean tree (regenerate a lost seed) → New Feature.
|
|
227
|
-
7. **TODO.md present** → Grilling / Grilled.
|
|
228
|
-
8. **Boundary or `gtd: package done` HEAD + clean tree** → Clean (review the
|
|
229
|
-
work), **Health check** (run `testCommand` when there is nothing to review —
|
|
230
|
-
on any branch outside a process), or Idle (health check green, nothing to
|
|
231
|
-
do).
|
|
232
|
-
|
|
233
|
-
Anything matching no rule is corruption — `gtd` **hard-errors** rather than
|
|
234
|
-
guess.
|
|
235
|
-
|
|
236
|
-
```mermaid
|
|
237
|
-
flowchart TD
|
|
238
|
-
Start([Run gtd]) --> P0{"HEAD = gtd: transport?"}
|
|
239
|
-
P0 -->|yes| Transport["Transport — mixed-reset HEAD, re-derive"]:::edge
|
|
240
|
-
Transport -.->|re-resolve| Start
|
|
241
|
-
P0 -->|no| P1{"ERRORS.md?"}
|
|
242
|
-
P1 -->|yes| Escalate["Escalate — STOP, human gate"]:::gate
|
|
243
|
-
P1 -->|no| P2{"FEEDBACK.md?"}
|
|
244
|
-
P2 -->|"empty = approval"| Close["Close package — rm pkg dir, gtd: package done"]:::edge
|
|
245
|
-
P2 -->|"non-empty"| Fixing["Fixing — rm FEEDBACK, fixer agent"]:::agent
|
|
246
|
-
P2 -->|absent| P2b{"HEALTH.md?"}
|
|
247
|
-
P2b -->|"present"| HealthFix["Health Fixing — rm HEALTH.md, gtd: health-check, fixer agent (leaves edits uncommitted)"]:::agent
|
|
248
|
-
HealthFix -.->|"next gtd run: dirty health HEAD → commit gtd: health-fix, re-resolve"| HealthCheck
|
|
249
|
-
P2b -->|absent| P3{".gtd/?"}
|
|
250
|
-
P3 -->|"modified"| Planning["Planning — gtd: planning"]:::agent
|
|
251
|
-
P3 -->|"code dirty / resume / no-op fixer"| Testing["Testing — gtd: building, run tests"]:::edge
|
|
252
|
-
P3 -->|"clean, HEAD planning/package done"| Building["Building — pick & build one package"]:::agent
|
|
253
|
-
P3 -->|"clean, HEAD building"| Review["Agentic Review — write FEEDBACK.md"]:::agent
|
|
254
|
-
P3 -->|absent| P4{"REVIEW.md?"}
|
|
255
|
-
P4 -->|"committed + clean or checkbox-only edits"| Done["Done — rm REVIEW, gtd: done"]:::edge
|
|
256
|
-
P4 -->|"committed + non-checkbox edits"| Accept["Accept Review — seed TODO, checkout, rm REVIEW"]:::edge
|
|
257
|
-
P4 -->|"uncommitted"| Await["Await Review — commit gtd: awaiting review"]:::edge
|
|
258
|
-
Await -.->|"re-resolve"| Done
|
|
259
|
-
P4 -->|absent| P5{"boundary HEAD + dirty,<br/>or gtd: new task + clean?"}
|
|
260
|
-
Done -->|"squash enabled"| Squashing["Squashing — agent authors conventional-commits message, reset --soft base, squash commit"]:::agent
|
|
261
|
-
Done -->|"squash disabled"| Idle
|
|
262
|
-
Squashing --> Idle["Idle — nothing to do (STOP)"]:::gate
|
|
263
|
-
P5 -->|yes| NewFeature["New Feature — gtd: new task, revert, seed TODO"]:::edge
|
|
264
|
-
P5 -->|no| P6{"TODO.md?"}
|
|
265
|
-
P6 -->|"open markers"| GrillStop["Grilling — gtd: grilling, STOP for answers"]:::gate
|
|
266
|
-
P6 -->|"dirty, no markers"| GrillIter["Grilling — gtd: grilling, agent iterates"]:::agent
|
|
267
|
-
P6 -->|"clean, no markers"| Grilled["Grilled — gtd: grilled, decompose"]:::agent
|
|
268
|
-
P6 -->|absent| P7{"clean + boundary/package-done HEAD,<br/>reviewable diff?"}
|
|
269
|
-
P7 -->|yes| CleanState["Clean — write REVIEW.md"]:::agent
|
|
270
|
-
P7 -->|"no (idle, outside a process)"| HealthCheck["Health check — run testCommand"]:::edge
|
|
271
|
-
HealthCheck -->|"green, no health-fix"| Idle
|
|
272
|
-
HealthCheck -->|"green + ≥1 health-fix, squash enabled"| Squashing
|
|
273
|
-
HealthCheck -->|"red, below cap"| HealthMd["write HEALTH.md, gtd: health-check"]:::edge
|
|
274
|
-
HealthMd -.->|"re-resolve"| HealthFix
|
|
275
|
-
HealthCheck -->|"red, at cap"| ErrorsMd["write ERRORS.md, gtd: health-check"]:::edge
|
|
276
|
-
ErrorsMd -.->|"re-resolve"| Escalate
|
|
277
|
-
classDef edge fill:#1a4a6b,color:#fff
|
|
278
|
-
classDef agent fill:#2d6a4f,color:#fff
|
|
279
|
-
classDef gate fill:#7a3b1d,color:#fff
|
|
192
|
+
**Agent tail lines.** In plain-mode output, a prompt for the **agent** actor
|
|
193
|
+
ends with the pinned tail:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Finish your turn by running `gtd step-agent`. Then run `gtd next` and follow
|
|
197
|
+
its output — repeat this cycle as long as the output is addressed to you (the
|
|
198
|
+
agent); when it awaits the human, stop and hand off.
|
|
280
199
|
```
|
|
281
200
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
201
|
+
The first sentence closes the current turn; the second closes the outer loop —
|
|
202
|
+
it is what lets a plain-text agent chain multiple iterations (e.g. successive
|
|
203
|
+
test/fix cycles) without an external driver, until a human gate is reached.
|
|
204
|
+
Human-actor prompts carry no tail. `--json` output never embeds the tail into
|
|
205
|
+
`prompt` either — the structured `actor` field (see JSON schemas below) carries
|
|
206
|
+
the same information: `"agent"` means another agent round, `"human"` means stop
|
|
207
|
+
and hand off.
|
|
208
|
+
|
|
209
|
+
### `gtd status`
|
|
285
210
|
|
|
286
|
-
|
|
211
|
+
Pure, read-only **dry-run prediction** — the same gather+resolve `gtd next`
|
|
212
|
+
runs, but reporting a prediction of the next turn rather than the actual prompt.
|
|
213
|
+
Performs no git mutation, no test run, no file write — guaranteed side-effect
|
|
214
|
+
free, including on a dirty tree.
|
|
287
215
|
|
|
288
|
-
|
|
289
|
-
|
|
216
|
+
Prints four fields:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
State: grilling
|
|
220
|
+
Awaits: human
|
|
221
|
+
Predicted commit: gtd(human): grilling
|
|
222
|
+
Predicted state: grilling
|
|
223
|
+
```
|
|
290
224
|
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
-
|
|
294
|
-
|
|
295
|
-
-
|
|
296
|
-
- ERRORS.md without .gtd
|
|
297
|
-
- HEALTH.md + .gtd (health check only runs from a bare idle tree)
|
|
298
|
-
- HEALTH.md + REVIEW.md (same)
|
|
299
|
-
- HEALTH.md + FEEDBACK.md (same)
|
|
300
|
-
- HEALTH.md + ERRORS.md (escalation wins; HEALTH.md must not coexist)
|
|
225
|
+
- **State** — the currently resolved state.
|
|
226
|
+
- **Awaits** — the actor (`human` or `agent`) whose turn it is.
|
|
227
|
+
- **Predicted commit** — the subject `step`/`step-agent` would author next, or
|
|
228
|
+
`(none)` at a fixpoint (e.g. idle with nothing to do).
|
|
229
|
+
- **Predicted state** — the state that commit would land in.
|
|
301
230
|
|
|
302
|
-
|
|
303
|
-
**Planning** only — TODO.md is deleted at the first Building turn);
|
|
304
|
-
FEEDBACK.md + `.gtd` (a fix during build).
|
|
231
|
+
`gtd status` takes no arguments — extra positional args are rejected.
|
|
305
232
|
|
|
306
|
-
###
|
|
233
|
+
### `gtd review <target>`
|
|
307
234
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
235
|
+
A pure mutator that **anchors, then exits** — it never prints a prompt itself.
|
|
236
|
+
Use it to start an ad-hoc human review against an explicit git ref or branch,
|
|
237
|
+
independent of the automatic review base the workflow otherwise computes.
|
|
311
238
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
239
|
+
1. Refuses on a dirty tree.
|
|
240
|
+
2. Resolves `<target>` via merge-base semantics and computes the diff HEAD adds
|
|
241
|
+
over `merge-base(<target>, HEAD)`.
|
|
242
|
+
3. Refuses if that diff is empty after filtering ("nothing to review").
|
|
243
|
+
4. Authors exactly one commit: `gtd: reviewing <full-hash-of-the-base>`.
|
|
244
|
+
5. Prints a short confirmation pointing at `gtd next` — it does **not** print
|
|
245
|
+
the review prompt itself.
|
|
315
246
|
|
|
316
247
|
```bash
|
|
317
|
-
|
|
318
|
-
|
|
248
|
+
gtd review main
|
|
249
|
+
# anchored review at <hash> — run `gtd next` to get the review prompt
|
|
250
|
+
gtd next --json
|
|
251
|
+
# {"actor":"agent", ...} — the review-record prompt scoped to that anchor
|
|
319
252
|
```
|
|
320
253
|
|
|
321
|
-
|
|
322
|
-
**Transport** state consumes it: on the far side, the next `gtd` run sees the
|
|
323
|
-
`gtd: transport` HEAD, mixed-resets it (`git reset HEAD~1`) to drop the work
|
|
324
|
-
back into the working tree uncommitted, and re-derives state from scratch. If
|
|
325
|
-
the transport commit is the repository's root commit (no parent), `gtd` fails
|
|
326
|
-
immediately with a clear error instead of looping.
|
|
327
|
-
|
|
328
|
-
## The 19 states
|
|
329
|
-
|
|
330
|
-
Each state has a **condition** (when it wins), a deterministic **action**, the
|
|
331
|
-
**commit(s)** it produces, and where it **advances**. States marked
|
|
332
|
-
**auto-advance** re-run `gtd` themselves; **STOP** states hand control to a
|
|
333
|
-
human; **edge-only** states render no prompt at all — the driver performs their
|
|
334
|
-
action and re-resolves silently.
|
|
335
|
-
|
|
336
|
-
| State | Kind | Wins when | Action & commit | Advances to |
|
|
337
|
-
| ------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
338
|
-
| **Transport** | edge-only, auto | HEAD `gtd: transport` (hand-made handoff commit) | mixed-reset HEAD (`git reset HEAD~1`), keep work in tree; **no commit** | re-derive from the restored tree |
|
|
339
|
-
| **Escalate** | STOP | ERRORS.md present | none | held until the human deletes ERRORS.md |
|
|
340
|
-
| **Fixing** | agent, auto | non-empty FEEDBACK.md present | inline FEEDBACK into the prompt, remove FEEDBACK.md; commit its removal `gtd: fixing` (FEEDBACK was committed by Testing) or `gtd: feedback` (uncommitted, written by Agentic Review) | fixer edits → Testing |
|
|
341
|
-
| **Health Fixing** | agent, auto | HEALTH.md present (no `.gtd`, REVIEW.md, or FEEDBACK.md) | read HEALTH.md into the prompt, commit its removal `gtd: health-check` (HEALTH.md removed so next resolve re-enters `resolveCleanOrIdle`); fixer leaves edits **uncommitted** | fixer edits uncommitted → next `gtd` invocation commits them `gtd: health-fix` → Health check |
|
|
342
|
-
| **Close package** | edge-only, auto | empty FEEDBACK.md present (clean review); also reached from Agentic Review force-approve | rm FEEDBACK.md, rm the first (finished) package dir (+ the now-empty `.gtd/`); commit `gtd: package done` | more packages → Building; `.gtd` gone → Clean |
|
|
343
|
-
| **Planning** | agent, auto | `.gtd` present **and modified**; HEAD `gtd: grilled` or `gtd: planning` | commit the `.gtd/` changes `gtd: planning` | continue decomposing, else → Building |
|
|
344
|
-
| **Testing** | edge-only, auto | `.gtd` present, no FEEDBACK/ERRORS, and a reason to test: code changes, a pending ERRORS.md deletion (human resume), or a clean tree under HEAD `gtd: fixing` (no-op fixer) | commit pending tree `gtd: building`, run `testCommand`; green → proceed; red → write FEEDBACK (below cap) or ERRORS (at cap), commit `gtd: errors`; if captured output is empty/whitespace, a sentinel string is written so the file is never empty (empty FEEDBACK remains reserved for agentic-review approval) | green → Agentic Review; FEEDBACK → Fixing; ERRORS → Escalate |
|
|
345
|
-
| **Building** | agent, auto | `.gtd` present and clean, clean tree; HEAD `gtd: planning` or `gtd: package done` | if HEAD `gtd: planning` and TODO.md present, delete TODO.md and commit (prefix unchanged, fires once); select the first package, inline its tasks; agent leaves work **uncommitted** | Testing |
|
|
346
|
-
| **Agentic Review** | agent, auto | `.gtd` present and clean, clean tree; HEAD `gtd: building` | reviewer writes FEEDBACK.md (empty = approval), uncommitted — **unless** force-approved (kill-switch off or review-fix threshold hit), which routes straight to Close package | empty FEEDBACK → Close package; non-empty → Fixing |
|
|
347
|
-
| **Done** | edge-only, auto | REVIEW.md committed + clean tree, **or** committed + checkbox-only edits (only `- [ ]`→`- [x]` flips in REVIEW.md = approval) | rm REVIEW.md, commit `gtd: done` | Squashing (if enabled) or Idle |
|
|
348
|
-
| **Squashing** | agent, auto | no steering files, HEAD `gtd: done` or green Health check with ≥1 `gtd: health-fix`, `squash` enabled, squash base present, no unrelated code dirty (a lone untracked `SQUASH_MSG.md` is allowed) | agent authors a conventional-commits message from the full `<base>..HEAD` diff, then runs `git reset --soft <base>` + `git commit` — collapses all intermediate `gtd: *` commits (including any interleaved non-gtd commits) into one; **gtd then STOPs** — post-squash review fires only on the next manual `gtd` run | Idle (STOP) |
|
|
349
|
-
| **Accept Review** | edge-only, auto | REVIEW.md committed + pending **non-checkbox** edits (human annotated REVIEW.md with comments / edited code) | seed TODO.md from the changeset, `git checkout` to discard the code edits, rm REVIEW.md; **all uncommitted** | Grilling |
|
|
350
|
-
| **Await Review** | edge-only, auto | REVIEW.md present and **uncommitted** (freshly written by Clean) | commit REVIEW.md `gtd: awaiting review` | Done (auto, same run) |
|
|
351
|
-
| **New Feature** | edge-only, auto | boundary HEAD + pending changes (code and/or a new uncommitted TODO.md), **or** HEAD `gtd: new task` + clean tree (lost-seed regen) | commit the raw input verbatim `gtd: new task` (unless already there), `git revert --no-commit` it back to a clean baseline, seed TODO.md from that diff — revert + seed left **uncommitted** | Grilling |
|
|
352
|
-
| **Grilling** | agent (iterate) / STOP (answers) | TODO.md present, not New Feature | commit pending edits `gtd: grilling`. Open-question markers present → STOP for the human to answer inline; no markers but dirty → grilling agent iterates | converge (no markers, clean tree) → Grilled |
|
|
353
|
-
| **Grilled** | agent, auto | TODO.md present, no markers, clean tree | commit pending `gtd: grilled` | decompose into `.gtd/` → Planning |
|
|
354
|
-
| **Clean** | agent | no steering files, clean tree, boundary or `gtd: package done` HEAD, and the review base yields a **non-empty** diff | compute the review base (three rules — see below); agent writes REVIEW.md **uncommitted** with `# Review: <short-hash>` heading, `<!-- base: <full-hash> -->` marker, and per-hunk `- [ ]` checkboxes (ticking them signals approval → Done) | Await Review |
|
|
355
|
-
| **Health check** | edge-only, auto | no steering files, outside a process with no reviewable diff (any branch) — the `!reviewable` case from rule 8. Two entry points: (a) clean tree under a boundary or `gtd: package done` HEAD; (b) **dirty tree under a `gtd: health-check` or `gtd: health-fix` HEAD with `!pendingErrorsDeletion`** (the fixer's uncommitted edits — commits them `gtd: health-fix` and re-runs the health check within the same `gtd` invocation; NOT corruption). | run `testCommand` (entry point a); or commit pending edits `gtd: health-fix`, then run `testCommand` (entry point b). green + no prior `gtd: health-fix` → Idle (no commit); red below `fixAttemptCap` → write HEALTH.md, commit `gtd: health-check` → Health Fixing; red at cap → write ERRORS.md, commit `gtd: health-check` → Escalate; green + ≥1 `gtd: health-fix` → Squashing (if `squash`) or Idle | green → Idle or Squashing; red below cap → Health Fixing; red at cap → Escalate |
|
|
356
|
-
| **Idle** | STOP | no steering files, clean tree, health check passed with no prior `gtd: health-fix` commits, and nothing to review | none (no commit — the health check edge terminates the driver loop directly) | — |
|
|
357
|
-
|
|
358
|
-
Every prompt also embeds the current `git diff HEAD` (untracked files included)
|
|
359
|
-
inline, plus the last commit subject and working-tree status, so the agent has
|
|
360
|
-
full context.
|
|
361
|
-
|
|
362
|
-
### Review base — three rules
|
|
363
|
-
|
|
364
|
-
The review base (the commit whose diff to HEAD forms the REVIEW.md) is chosen by
|
|
365
|
-
three rules evaluated in priority order:
|
|
366
|
-
|
|
367
|
-
1. **Within a process, first review** — a `gtd: grilling` commit exists after
|
|
368
|
-
the last `gtd: done` (or task start), but no `gtd: awaiting review` yet →
|
|
369
|
-
base = first `gtd: grilling` of the current task cycle; `refDiff` spans the
|
|
370
|
-
whole task.
|
|
371
|
-
2. **Within a process, incremental** — `gtd: awaiting review` also present in
|
|
372
|
-
the current cycle (takes precedence over rule 1) → base = last
|
|
373
|
-
`gtd: awaiting review`; `refDiff` spans only the post-review changes.
|
|
374
|
-
3. **Outside a process (any branch)** — no `gtd: grilling` after the last
|
|
375
|
-
`gtd: done` → skip review; `reviewBase`/`refDiff` unset → Idle (the health
|
|
376
|
-
check runs instead).
|
|
377
|
-
|
|
378
|
-
In all cases, if the diff from the chosen base to HEAD is empty,
|
|
379
|
-
`reviewBase`/`refDiff` are left unset and the machine settles in Idle.
|
|
380
|
-
|
|
381
|
-
## The fix loops & counter folds
|
|
382
|
-
|
|
383
|
-
Three derived counters drive the budgeted loops. All are **folded in the
|
|
384
|
-
machine** from flags on the `COMMIT[]` stream — never recomputed at the edge:
|
|
385
|
-
|
|
386
|
-
- **`testFixCount`** — `gtd: errors` commits (test-fix attempts) since the
|
|
387
|
-
**most recent of** {a package start (`gtd: planning` / `gtd: package done`), a
|
|
388
|
-
`gtd: feedback` (start of a review-fix), or a commit that **removed
|
|
389
|
-
ERRORS.md** (a human resume)}. So each test-fix sub-loop, each review-fix
|
|
390
|
-
round, and every human resume starts a **fresh budget**.
|
|
391
|
-
- **`reviewFixCount`** — `gtd: feedback` commits (review-fix rounds) since the
|
|
392
|
-
most recent package start.
|
|
393
|
-
- **`healthFixCount`** — `gtd: health-check` commits since the most recent
|
|
394
|
-
commit that removed HEALTH.md (or the start of branch history if none). Reuses
|
|
395
|
-
`fixAttemptCap` — no separate config key.
|
|
396
|
-
|
|
397
|
-
### Test-fix loop (`fixAttemptCap`, default 3)
|
|
398
|
-
|
|
399
|
-
When Testing's run is red, it writes the captured output and commits
|
|
400
|
-
`gtd: errors`, incrementing `testFixCount`. If the captured output is empty or
|
|
401
|
-
whitespace-only (e.g. a command that exits non-zero with no output), a sentinel
|
|
402
|
-
string is written instead — so FEEDBACK/ERRORS is never empty. Empty FEEDBACK
|
|
403
|
-
remains reserved exclusively for Agentic Review's deliberate approval signal.
|
|
254
|
+
Errors (all exit 1, message on stderr):
|
|
404
255
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
256
|
+
- Missing target: `gtd review: missing target argument`
|
|
257
|
+
- Extra arguments:
|
|
258
|
+
`gtd review: too many arguments — expected one target, got: …`
|
|
259
|
+
- Unresolvable ref: `gtd review: cannot resolve ref '<target>': <error message>`
|
|
260
|
+
- Empty diff:
|
|
261
|
+
`gtd review: nothing to review (<target> diff is empty after filtering)`
|
|
411
262
|
|
|
412
|
-
|
|
413
|
-
or over the cap (`testFixCount >= fixAttemptCap`), it goes to **ERRORS.md**
|
|
414
|
-
instead and the loop **stops** at Escalate. The human investigates, then deletes
|
|
415
|
-
ERRORS.md — which **resets the fix-attempt budget** (the next run re-tests and
|
|
416
|
-
grants a fresh `cap` attempts before escalating again). While ERRORS.md exists,
|
|
417
|
-
every run resolves straight back to Escalate.
|
|
263
|
+
### `gtd format <file>`
|
|
418
264
|
|
|
419
|
-
|
|
265
|
+
Unchanged from v1: formats a markdown file in place with a bundled prettier
|
|
266
|
+
(`parser: "markdown"`, `printWidth: 80`, `proseWrap: "always"`), ignoring the
|
|
267
|
+
host repo's own `.prettierrc` so `.gtd/TODO.md`/`.gtd/REVIEW.md` stay
|
|
268
|
+
consistently formatted regardless of the host project's toolchain. Rejects
|
|
269
|
+
`--json` (exit 1, `gtd format does not accept --json`) — it is a plain file
|
|
270
|
+
operation, not a v2 state command.
|
|
420
271
|
|
|
421
|
-
|
|
422
|
-
files, `gtd` runs `testCommand` instead of stopping. This reuses `fixAttemptCap`
|
|
423
|
-
(default 3) and `squash` — no new config keys are introduced.
|
|
272
|
+
Errors (all exit 1, message on stderr):
|
|
424
273
|
|
|
274
|
+
- Missing path: `gtd format: missing file path argument`
|
|
275
|
+
- Extra arguments: `gtd format: too many arguments — expected one path, got: …`
|
|
276
|
+
- Non-markdown file:
|
|
277
|
+
`gtd format: <file> is not a markdown file (expected .md or .markdown)`
|
|
278
|
+
- File not found: `gtd: skipped formatting <file>: not found`
|
|
279
|
+
|
|
280
|
+
## JSON schemas
|
|
281
|
+
|
|
282
|
+
Pass `--json` to `step`, `step-agent`, `next`, or `status` for machine-readable
|
|
283
|
+
single-line JSON output instead of plain text.
|
|
284
|
+
|
|
285
|
+
**`step` / `step-agent`** — `{state, actions, commits}`:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"state": "grilled",
|
|
290
|
+
"actions": ["capture the human turn as \"gtd(human): grilling\""],
|
|
291
|
+
"commits": ["gtd(human): grilling", "gtd: grilled"]
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- `state` — the final resolved state after the fixpoint loop settled.
|
|
296
|
+
- `actions` — human-readable descriptions of every edge action this invocation
|
|
297
|
+
performed, oldest→newest.
|
|
298
|
+
- `commits` — every commit subject this invocation authored, oldest→newest.
|
|
299
|
+
|
|
300
|
+
**`next`** — `{state, actor, pending, prompt}`:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"state": "building",
|
|
305
|
+
"actor": "agent",
|
|
306
|
+
"pending": false,
|
|
307
|
+
"prompt": "..."
|
|
308
|
+
}
|
|
425
309
|
```
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
310
|
+
|
|
311
|
+
- `state` — the resolved state.
|
|
312
|
+
- `actor` — `"human"` or `"agent"`: who owns the next move. This is the single
|
|
313
|
+
loop-driver signal: `"agent"` means proceed with another round — act on
|
|
314
|
+
`prompt` when present, then run `gtd step-agent`; at an agent-driven pending
|
|
315
|
+
checkpoint (`prompt` is `null`, nothing to act on) just run `gtd step-agent`.
|
|
316
|
+
`"human"` means halt and hand off (a human rest, whose prompt body already
|
|
317
|
+
tells the human what to do, or a human-driven pending checkpoint resumed by
|
|
318
|
+
`gtd step`).
|
|
319
|
+
- `pending` — `true` at a mid-chain HEAD (no prompt yet — resume with a mutator
|
|
320
|
+
first); `false` at a genuine rest.
|
|
321
|
+
- `prompt` — the full prompt markdown when `pending` is `false`, else `null`.
|
|
322
|
+
|
|
323
|
+
**`status`** — `{state, actor, predictedCommit, predictedState}`:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"state": "grilling",
|
|
328
|
+
"actor": "human",
|
|
329
|
+
"predictedCommit": "gtd(human): grilling",
|
|
330
|
+
"predictedState": "grilling"
|
|
331
|
+
}
|
|
436
332
|
```
|
|
437
333
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
(an edge-only `health-check` state with a `commitPending` action), and
|
|
444
|
-
immediately re-runs the health check within that same invocation.
|
|
445
|
-
- **red, at or over `fixAttemptCap`** → write test output to ERRORS.md, commit
|
|
446
|
-
`gtd: health-check` → **Escalate** (human gate). Delete ERRORS.md to reset the
|
|
447
|
-
budget and resume.
|
|
448
|
-
- **green, ≥1 `gtd: health-fix` present** → health-fix cycle converged: if
|
|
449
|
-
`squash` is enabled → **Squashing** (squash base = parent of the first
|
|
450
|
-
`gtd: health-check` of the current run); otherwise → Idle (no commit).
|
|
451
|
-
|
|
452
|
-
### Review-fix loop & agentic review (`reviewThreshold`, default 3)
|
|
453
|
-
|
|
454
|
-
After a green test run, **Agentic Review** reviews the package's accumulated
|
|
455
|
-
diff against its task specs and **always writes FEEDBACK.md**:
|
|
334
|
+
`predictedCommit` is `null` when the next invocation would author nothing (e.g.
|
|
335
|
+
idle with a green health check).
|
|
336
|
+
|
|
337
|
+
**Error envelope** — every command, in `--json` mode, reports failures inside
|
|
338
|
+
the JSON object rather than as unstructured text, and still exits 1:
|
|
456
339
|
|
|
340
|
+
```json
|
|
341
|
+
{ "state": "error", "prompt": "<message>" }
|
|
457
342
|
```
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
343
|
+
|
|
344
|
+
There is no auto-advance flag anywhere in the wire format — `actor` replaces it.
|
|
345
|
+
The caller decides whether to keep looping based on `actor` (halt on `"human"`)
|
|
346
|
+
and `pending` (re-run `step`/`step-agent` first when `true`), not on a boolean
|
|
347
|
+
auto-advance flag.
|
|
348
|
+
|
|
349
|
+
## The reference loop driver
|
|
350
|
+
|
|
351
|
+
A minimal bash implementation of the pinned two-beat protocol, driving an agent
|
|
352
|
+
CLI (e.g. `claude -p`) against `gtd --json` output. This is the authoritative
|
|
353
|
+
reference for what a loop driver must do; keep any other implementation
|
|
354
|
+
(including `skills/loop/SKILL.md`) consistent with it rather than editing both
|
|
355
|
+
independently.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
#!/usr/bin/env bash
|
|
359
|
+
set -euo pipefail
|
|
360
|
+
|
|
361
|
+
while true; do
|
|
362
|
+
# 1. Advance the machine's own agent-owned bookkeeping to a fixpoint.
|
|
363
|
+
gtd step-agent --json >/dev/null || true
|
|
364
|
+
|
|
365
|
+
# 2. Ask who's up next. `actor` is the single "proceed" signal.
|
|
366
|
+
next="$(gtd next --json)"
|
|
367
|
+
actor="$(jq -r .actor <<<"$next")"
|
|
368
|
+
prompt="$(jq -r .prompt <<<"$next")"
|
|
369
|
+
|
|
370
|
+
if [[ "$actor" != "agent" ]]; then
|
|
371
|
+
echo "Halting — the human owns the next move."
|
|
372
|
+
break
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
if [[ "$prompt" == "null" ]]; then
|
|
376
|
+
# Agent-driven pending checkpoint: nothing to act on — loop back to
|
|
377
|
+
# step 1, whose `gtd step-agent` resumes the mid-chain bookkeeping.
|
|
378
|
+
continue
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Agent's turn: feed the prompt to the agent, then let it finish with
|
|
382
|
+
# `gtd step-agent` itself (the prompt's tail instructs it to).
|
|
383
|
+
claude -p "$prompt" --dangerously-skip-permissions
|
|
384
|
+
done
|
|
461
385
|
```
|
|
462
386
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
`
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
387
|
+
The agent is expected to run `gtd step-agent` itself once it finishes acting on
|
|
388
|
+
the prompt (the plain-mode tail says exactly this) — the driver's own
|
|
389
|
+
`step-agent` calls exist to advance any bookkeeping the agent doesn't own
|
|
390
|
+
(routing commits, test runs) between agent turns.
|
|
391
|
+
|
|
392
|
+
The loop halts on `actor: "human"` alone: a human rest (`pending: false`, the
|
|
393
|
+
prompt body addresses the human) or a human-driven pending checkpoint
|
|
394
|
+
(`pending: true`, resumed by the human's own `gtd step`). Everything the agent
|
|
395
|
+
side can drive — agent rests and agent-driven checkpoints — reports
|
|
396
|
+
`actor: "agent"`, so multiple agent turns and commits (e.g. successive test/fix
|
|
397
|
+
cycles, a force-approved package close) chain without human involvement until an
|
|
398
|
+
actual human gate is hit.
|
|
399
|
+
|
|
400
|
+
## States & subjects overview
|
|
401
|
+
|
|
402
|
+
`resolve()` lands on exactly one of **16 states**: `grilling`, `grilled`,
|
|
403
|
+
`planning`, `building`, `testing`, `fixing`, `escalate`, `agentic-review`,
|
|
404
|
+
`close-package`, `review`, `await-review`, `done`, `squashing`, `idle`,
|
|
405
|
+
`health-check`, `health-fixing`. Each state has a fixed awaited actor (see
|
|
406
|
+
`awaitedActor` in `src/Machine.ts`): `idle`, `escalate`, and `await-review`
|
|
407
|
+
await the **human**; every other state awaits the **agent**.
|
|
408
|
+
|
|
409
|
+
For the full precedence ladder, illegal combinations, and the counter folds that
|
|
410
|
+
drive the fix loops, see [STATES.md](STATES.md) — this section is a summary.
|
|
411
|
+
|
|
412
|
+
### Turn commits — `gtd(<actor>): <gate>`
|
|
413
|
+
|
|
414
|
+
Authored by `gtd step`/`gtd step-agent` as the first commit of a fresh chain.
|
|
415
|
+
The closed set of gates:
|
|
416
|
+
|
|
417
|
+
| Gate | Authored by |
|
|
418
|
+
| ---------------- | ------------------------------------------------------------------ |
|
|
419
|
+
| `grilling` | human (answers) / agent (plan iteration) |
|
|
420
|
+
| `grilled` | agent (converged, ready to decompose) |
|
|
421
|
+
| `building` | agent (package work, or human feedback while agent is out of turn) |
|
|
422
|
+
| `fixing` | agent (test-fix or review-fix round) |
|
|
423
|
+
| `agentic-review` | agent (writes .gtd/FEEDBACK.md verdict) |
|
|
424
|
+
| `review` | agent (writes .gtd/REVIEW.md) / human (approves or gives feedback) |
|
|
425
|
+
| `squashing` | agent (overwrites .gtd/SQUASH_MSG.md) |
|
|
426
|
+
| `health-fixing` | agent (idle health-check repair) |
|
|
427
|
+
| `escalate` | human (deletes .gtd/ERRORS.md to resume) |
|
|
428
|
+
|
|
429
|
+
### Routing commits — `gtd: <phase>`
|
|
430
|
+
|
|
431
|
+
Bookkeeping the machine authors itself between turns, never a turn a human or
|
|
432
|
+
agent "wins": `gtd: grilled`, `gtd: planning`, `gtd: tests green`,
|
|
433
|
+
`gtd: errors`, `gtd: package done`, `gtd: awaiting review`,
|
|
434
|
+
`gtd: review feedback`, `gtd: done`, `gtd: squash template`,
|
|
435
|
+
`gtd: reviewing <hash>` (parameterized, from `gtd review`), `gtd: health-check`,
|
|
436
|
+
`gtd: health-fix`.
|
|
437
|
+
|
|
438
|
+
Everything else — any non-`gtd` subject, and any `gtd: *` subject outside this
|
|
439
|
+
closed set — is a **boundary commit**: inert as far as the machine's grammar is
|
|
440
|
+
concerned. See [Upgrading from v1](#upgrading-from-v1-breaking-change) for why
|
|
441
|
+
this matters on upgrade.
|
|
442
|
+
|
|
443
|
+
## Workflow walkthroughs
|
|
444
|
+
|
|
445
|
+
### Grilling
|
|
446
|
+
|
|
447
|
+
A dirty tree at a boundary HEAD (a fresh idea, sketched in a file or just left
|
|
448
|
+
as pending code) is captured in **one** human turn: `gtd step` commits
|
|
449
|
+
everything pending as `gtd(human): grilling` — nothing is reverted or seeded,
|
|
450
|
+
the captured files stay in history. `gtd next` hands the agent that turn's diff;
|
|
451
|
+
the agent develops `.gtd/TODO.md` into a concrete plan **in one turn**,
|
|
452
|
+
proposing a **suggested default** for every open question, and leaves
|
|
453
|
+
`.gtd/TODO.md` uncommitted for `gtd(agent): grilling`.
|
|
454
|
+
|
|
455
|
+
There are no markers to answer — the human either:
|
|
456
|
+
|
|
457
|
+
- **Accepts the suggested defaults**: runs a clean `gtd step` at the answer
|
|
458
|
+
gate. An empty `gtd(human): grilling` turn plus routing `gtd: grilled` lands
|
|
459
|
+
automatically, and `gtd next` emits the decompose prompt.
|
|
460
|
+
- **Edits `.gtd/TODO.md`** with real answers, then runs `gtd step`, which
|
|
461
|
+
captures the edit as a fresh `gtd(human): grilling` turn and hands it back to
|
|
462
|
+
the agent for another round.
|
|
463
|
+
|
|
464
|
+
### Build lifecycle: budgets
|
|
465
|
+
|
|
466
|
+
Once decomposed, `.gtd/` holds ordered work packages. `gtd next` at
|
|
467
|
+
`gtd: planning`/`gtd: package done` selects the lowest-numbered remaining
|
|
468
|
+
package and inlines only its task files. The agent builds it and leaves the work
|
|
469
|
+
**uncommitted**; the next invocation's edge action commits it (the
|
|
470
|
+
`gtd(agent): building` turn commit) and runs `testCommand`.
|
|
471
|
+
|
|
472
|
+
- **Green** → Agentic Review.
|
|
473
|
+
- **Red, below `fixAttemptCap`** (default 3) → write findings, commit
|
|
474
|
+
`gtd: errors`, rest at **Fixing** for the agent.
|
|
475
|
+
- **Red, at/over the cap** → write `.gtd/ERRORS.md` instead, commit
|
|
476
|
+
`gtd: errors`, rest at **Escalate** — a human gate. Deleting `.gtd/ERRORS.md`
|
|
477
|
+
and landing that deletion as `gtd(human): escalate` resets the budget and
|
|
478
|
+
re-tests from zero in the same invocation.
|
|
479
|
+
|
|
480
|
+
### Agentic review
|
|
481
|
+
|
|
482
|
+
A green test run always rests at **Agentic Review**: the agent reviews the
|
|
483
|
+
package's accumulated diff against its task specs and writes `.gtd/FEEDBACK.md`.
|
|
484
|
+
An **empty** `.gtd/FEEDBACK.md` is the approval signal — the same
|
|
485
|
+
`gtd(agent): agentic-review` turn closes the package (`gtd: package done`,
|
|
486
|
+
removing `.gtd/FEEDBACK.md` and the finished package directory) in one
|
|
487
|
+
invocation. Non-empty findings rest for the fixing prompt; fixing loops back
|
|
488
|
+
through the test gate and re-reviews. Once `reviewFixCount >= reviewThreshold`
|
|
489
|
+
(default 3) within a package, Agentic Review **force-approves** without ever
|
|
490
|
+
writing `.gtd/FEEDBACK.md` — so a package can never review-loop forever. The
|
|
491
|
+
findings round that crosses the threshold still gets its fixing round; the
|
|
492
|
+
force-approve close then fires at the next green re-test instead of another
|
|
493
|
+
review. (Any agentic-review turn that touches `.gtd/FEEDBACK.md` counts toward
|
|
494
|
+
the threshold — including the approval write itself; an approval that crosses
|
|
495
|
+
the threshold simply closes the package as usual.) Setting
|
|
496
|
+
`agenticReview: false` force-approves every package immediately.
|
|
497
|
+
|
|
498
|
+
A **do-nothing agent invocation** — `gtd step-agent` on a clean tree at ANY
|
|
499
|
+
agent-awaited rest whose move is a file artifact (`grilling`, `grilled`,
|
|
500
|
+
`building`, `fixing`, `agentic-review`, `review`, and `squashing` while
|
|
501
|
+
`.gtd/SQUASH_MSG.md` still holds the unmodified template) — is inert: zero
|
|
502
|
+
commits, no state consumed; `gtd next` re-emits the same prompt. This is
|
|
503
|
+
load-bearing for the loop protocol, whose every iteration opens with
|
|
504
|
+
`gtd step-agent` before the agent has acted: without the guard that opening beat
|
|
505
|
+
would author junk empty turns — and worse, consume workflow state (an empty
|
|
506
|
+
decompose turn would delete `.gtd/TODO.md` with no packages written; an empty
|
|
507
|
+
squashing turn would squash the cycle under the placeholder template). The same
|
|
508
|
+
guards hold at the classification layer for histories that already carry such
|
|
509
|
+
turns: a `gtd(agent): grilled` HEAD only routes to `gtd: planning` when packages
|
|
510
|
+
exist, a `gtd(agent): review` HEAD only routes to `gtd: awaiting review` when
|
|
511
|
+
`.gtd/REVIEW.md` exists, and a squashing turn only squashes once the template
|
|
512
|
+
has been overwritten. The one deliberate exception is `health-fixing`, whose
|
|
513
|
+
empty turn is meaningful (the failure may have been environmental — the machine
|
|
514
|
+
removes `.gtd/HEALTH.md` and re-tests). Human gates are unaffected: an empty
|
|
515
|
+
**human** turn stays a signal (accept-defaults at grilling, clean approval at
|
|
516
|
+
review).
|
|
517
|
+
|
|
518
|
+
### Human review gate
|
|
519
|
+
|
|
520
|
+
Once `.gtd/` is fully closed, the machine writes `.gtd/REVIEW.md` and rests at
|
|
521
|
+
**await-review**, awaiting the human. Approval is any of:
|
|
522
|
+
|
|
523
|
+
- A **clean** `gtd step` (nothing edited) — an empty `gtd(human): review` turn
|
|
524
|
+
plus routing `gtd: done`.
|
|
525
|
+
- Flipping only `- [ ]` → `- [x]` checkboxes in `.gtd/REVIEW.md` — checkbox-only
|
|
526
|
+
edits are also treated as clean approval.
|
|
527
|
+
|
|
528
|
+
Any **substantive** edit — to `.gtd/REVIEW.md` prose, or to the reviewed code
|
|
529
|
+
itself — is feedback: `gtd(human): review` plus routing `gtd: review feedback`,
|
|
530
|
+
`.gtd/REVIEW.md` removed, and `gtd next` re-emits a grilling prompt to the agent
|
|
531
|
+
that inlines the human's finding.
|
|
532
|
+
|
|
533
|
+
### Squash
|
|
534
|
+
|
|
535
|
+
With `squash: true` (the default), `gtd: done` is **not** a rest — the same
|
|
536
|
+
chain continues straight to `gtd: squash template`, writing and committing a
|
|
537
|
+
`.gtd/SQUASH_MSG.md` template. `gtd next` then emits the squashing prompt: the
|
|
538
|
+
agent overwrites `.gtd/SQUASH_MSG.md` with a real conventional-commits message
|
|
539
|
+
(drawing on grilling-round decisions from history) and finishes its turn.
|
|
540
|
+
`gtd step-agent` then performs the squash itself: `git reset --soft <base>` +
|
|
541
|
+
`git commit`, collapsing every intermediate `gtd: *` commit of the cycle into
|
|
542
|
+
one — including any review-feedback detours: the squash base is the cycle's
|
|
543
|
+
ORIGINAL start (the first grilling run since the previous `gtd: done` boundary,
|
|
544
|
+
or the `gtd: reviewing <hash>` anchor for an ad-hoc review cycle), not the most
|
|
545
|
+
recent re-grilling round — the collapse folds the whole cycle into one, using
|
|
546
|
+
the overwritten message's content verbatim (turn position, not message content,
|
|
547
|
+
triggers the squash). With `squash: false`, `gtd: done` is the resting boundary
|
|
548
|
+
and no template is ever written.
|
|
549
|
+
|
|
550
|
+
### Health check
|
|
551
|
+
|
|
552
|
+
Outside any process (idle, nothing to review, no steering files), `gtd step`
|
|
553
|
+
runs `testCommand` as a health check rather than settling immediately. Green
|
|
554
|
+
settles idle with zero commits. Red below `fixAttemptCap` writes
|
|
555
|
+
`.gtd/HEALTH.md` and rests at **Health Fixing** for the agent; the fixer's own
|
|
556
|
+
turn (`gtd(agent): health-fixing`) removes `.gtd/HEALTH.md` and re-tests in the
|
|
557
|
+
same chain — a green re-test continues to squash (if enabled) or idle; red
|
|
558
|
+
repeats the health-fix loop; red at the cap writes `.gtd/ERRORS.md` and
|
|
559
|
+
escalates.
|
|
560
|
+
|
|
561
|
+
### Escalate / budget reset
|
|
562
|
+
|
|
563
|
+
`.gtd/ERRORS.md` present is always a human gate, regardless of which loop wrote
|
|
564
|
+
it (test-fix or health-fix). Deleting `.gtd/ERRORS.md` and running `gtd step`
|
|
565
|
+
records the deletion as the human's `gtd(human): escalate` turn, which
|
|
566
|
+
**immediately re-tests in the same invocation** — this resets the relevant
|
|
567
|
+
fix-attempt budget to zero.
|
|
568
|
+
|
|
569
|
+
## States & subjects: overview table
|
|
570
|
+
|
|
571
|
+
| State | Awaits | Turn/routing subject at rest |
|
|
572
|
+
| ---------------- | -------------- | ---------------------------------------------------------------- |
|
|
573
|
+
| `grilling` | human or agent | `gtd(human): grilling` / `gtd(agent): grilling` |
|
|
574
|
+
| `grilled` | agent | `gtd: grilled` |
|
|
575
|
+
| `planning` | agent | `.gtd/` modified |
|
|
576
|
+
| `building` | agent | `gtd: planning` / `gtd: package done` |
|
|
577
|
+
| `testing` | — (edge-only) | mid-chain only |
|
|
578
|
+
| `fixing` | agent | `gtd: errors` |
|
|
579
|
+
| `escalate` | human | `.gtd/ERRORS.md` present |
|
|
580
|
+
| `agentic-review` | agent | `gtd: tests green` |
|
|
581
|
+
| `close-package` | — (edge-only) | mid-chain only |
|
|
582
|
+
| `review` | agent | `gtd: package done` (no more packages) / `gtd: reviewing <hash>` |
|
|
583
|
+
| `await-review` | human | `gtd: awaiting review` |
|
|
584
|
+
| `done` | — (edge-only) | `gtd: done` |
|
|
585
|
+
| `squashing` | agent | `gtd: squash template` |
|
|
586
|
+
| `idle` | human | no steering files, green health check |
|
|
587
|
+
| `health-check` | — (edge-only) | mid-chain only |
|
|
588
|
+
| `health-fixing` | agent | `.gtd/HEALTH.md` present |
|
|
589
|
+
|
|
590
|
+
See [STATES.md](STATES.md) for the full precedence ladder, the counter folds,
|
|
591
|
+
and every illegal steering-file combination.
|
|
534
592
|
|
|
535
593
|
## Configuration
|
|
536
594
|
|
|
@@ -548,37 +606,34 @@ built-in defaults apply. Supported filenames (searched in this order):
|
|
|
548
606
|
### Schema
|
|
549
607
|
|
|
550
608
|
- **`testCommand`** (string, default `npm run test`) — the command the edge runs
|
|
551
|
-
|
|
552
|
-
on the default-branch idle path.
|
|
609
|
+
after a build turn, and on the idle health-check path.
|
|
553
610
|
- **`fixAttemptCap`** (non-negative integer, default `3`) — the test-fix budget:
|
|
554
611
|
how many `gtd: errors` attempts are allowed per sub-loop before the failure is
|
|
555
|
-
escalated to ERRORS.md (Escalate). `0` disables the cap (escalates
|
|
556
|
-
on the first red run). Also reused as the health-fix budget
|
|
557
|
-
config key
|
|
612
|
+
escalated to `.gtd/ERRORS.md` (Escalate). `0` disables the cap (escalates
|
|
613
|
+
immediately on the first red run). Also reused as the health-fix budget — no
|
|
614
|
+
separate config key.
|
|
558
615
|
- **`reviewThreshold`** (integer ≥ 1, default `3`) — the review-fix budget: how
|
|
559
|
-
many
|
|
560
|
-
force-approves.
|
|
616
|
+
many agentic-review findings rounds are allowed per package before Agentic
|
|
617
|
+
Review force-approves.
|
|
561
618
|
- **`agenticReview`** (boolean, default `true`) — kill-switch for the
|
|
562
|
-
per-package Agentic Review gate. Set
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
`git reset --soft <base>` + `git commit`. Set `false` to keep the granular
|
|
568
|
-
history.
|
|
619
|
+
per-package Agentic Review gate. Set `false` to force-approve every package
|
|
620
|
+
and proceed directly to human review.
|
|
621
|
+
- **`squash`** (boolean, default `true`) — after `gtd: done`, collapse the
|
|
622
|
+
cycle's `gtd: *` commits into a single conventional-commits commit. Set
|
|
623
|
+
`false` to keep the granular history.
|
|
569
624
|
- **`models`** — model selection for the subagent-spawning states:
|
|
570
625
|
- `planning` — high-reasoning tier (default `claude-opus-4-8`), used by
|
|
571
|
-
`decompose
|
|
626
|
+
`decompose` (the `grilled`/`planning` states), `grilling`, `agentic-review`,
|
|
627
|
+
and `clean` (the `review`/`squashing` states).
|
|
572
628
|
- `execution` — everyday tier (default `claude-sonnet-4-8`), used by
|
|
573
629
|
`building` and `fixing`.
|
|
574
|
-
- `states.*` — per-state overrides keyed by
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
- **`$schema`** (string, optional) —
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
the package (and is published/committed on release).
|
|
630
|
+
- `states.*` — per-state overrides keyed by `decompose`, `grilling`,
|
|
631
|
+
`building`, `fixing`, `agentic-review`, `clean`. Unknown `states` keys are
|
|
632
|
+
**rejected**.
|
|
633
|
+
- **`$schema`** (string, optional) — stripped before validation, so it never
|
|
634
|
+
counts as an unknown key. Point it at the published schema for editor-backed
|
|
635
|
+
autocompletion. A `schema.json` is generated from the config schema at build
|
|
636
|
+
time and ships with the package.
|
|
582
637
|
|
|
583
638
|
### Validation and errors
|
|
584
639
|
|
|
@@ -586,50 +641,38 @@ If a config file fails to load or is invalid, gtd **exits with code 1** and
|
|
|
586
641
|
writes a human-readable error to **stderr** (never stdout):
|
|
587
642
|
|
|
588
643
|
- **Parse errors** (malformed YAML/JSON) — message includes the offending
|
|
589
|
-
filename
|
|
644
|
+
filename.
|
|
590
645
|
- **Non-object top-level** — a YAML list or `null` at the root is rejected with
|
|
591
646
|
the filename in the message.
|
|
592
647
|
- **Schema violations** — unknown keys or out-of-range values emit
|
|
593
|
-
`Invalid gtd config: <field>: <reason>`.
|
|
594
|
-
dump the full type tree.
|
|
648
|
+
`Invalid gtd config: <field>: <reason>`.
|
|
595
649
|
- **Missing test binary** — if `testCommand` names an executable that cannot be
|
|
596
|
-
found (`ENOENT`), gtd exits with
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
normal red-path (FEEDBACK → Fixing).
|
|
650
|
+
found (`ENOENT`), gtd exits 1 with `gtd: test command not found: <command>` on
|
|
651
|
+
stderr. A non-zero test _exit code_ is not an error — it drives the normal red
|
|
652
|
+
path.
|
|
600
653
|
|
|
601
654
|
### Lookup and precedence
|
|
602
655
|
|
|
603
656
|
gtd walks from the current working directory **up to your home directory** (or
|
|
604
657
|
to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
|
|
605
658
|
finds along the way. All found levels are **deep-merged**, with the **innermost
|
|
606
|
-
(cwd) config winning** on conflicts
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
parent directory and it cascades to **all** checkouts/worktrees beneath it,
|
|
610
|
-
while any individual checkout can still override settings with its own `.gtdrc`.
|
|
659
|
+
(cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
|
|
660
|
+
directory cascades to every checkout beneath it, while any individual checkout
|
|
661
|
+
can still override settings with its own `.gtdrc`.
|
|
611
662
|
|
|
612
663
|
### Auto-init
|
|
613
664
|
|
|
614
|
-
On every
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
out of the box; add any settings below the `$schema` line to override the
|
|
626
|
-
defaults.
|
|
627
|
-
|
|
628
|
-
Auto-init is skipped when HEAD is a `gtd: transport` commit: transport is a
|
|
629
|
-
consume-only handoff HEAD (mixed-reset in the Transport pre-pass), so committing
|
|
630
|
-
a config stub on top of it would displace the transport commit — and, when it is
|
|
631
|
-
the repository root, silently mask the "cannot reset transport commit" error.
|
|
632
|
-
The stub is created on a later run, once the transport HEAD has been consumed.
|
|
665
|
+
On every **state command** (`step`, `step-agent`, `next`, `status`, `review`)
|
|
666
|
+
that has passed the repo-root guard, if the cwd→root walk finds **no** config
|
|
667
|
+
anywhere, gtd creates and commits a starter `.gtdrc.json` at the repository root
|
|
668
|
+
containing only a `$schema` link. Auto-init never runs for `--version`/`--help`,
|
|
669
|
+
`format`, bare/unknown commands, or an invocation refused by the repo-root guard
|
|
670
|
+
— those perform no repository mutation of any kind. On a repo with no commits
|
|
671
|
+
yet, or whose HEAD is a plain (non-`gtd:`) commit, the stub is committed as its
|
|
672
|
+
own `chore: add .gtdrc.json`. If HEAD is already a `gtd:`-owned commit
|
|
673
|
+
(mid-workflow), the stub is instead **amended into HEAD** — stacking a fresh
|
|
674
|
+
boundary commit there would produce an unrecognized HEAD most workflow states
|
|
675
|
+
can't resolve past.
|
|
633
676
|
|
|
634
677
|
### Example
|
|
635
678
|
|
|
@@ -648,14 +691,32 @@ models:
|
|
|
648
691
|
building: claude-sonnet-4-8
|
|
649
692
|
```
|
|
650
693
|
|
|
651
|
-
##
|
|
694
|
+
## Repository requirements
|
|
695
|
+
|
|
696
|
+
- **Single writer, linear branch.** State is folded from **first-parent**
|
|
697
|
+
history only. A merge commit at HEAD is unsupported (documented, not handled)
|
|
698
|
+
— it degrades gracefully on the default branch rather than crashing, but do
|
|
699
|
+
not rely on merge commits mid-cycle.
|
|
700
|
+
- **Test/build artifacts must be gitignored.** This is **load-bearing**, not a
|
|
701
|
+
style preference: every fixpoint hop in `gtd step`/`gtd step-agent` detects
|
|
702
|
+
"clean" via `git status --porcelain`, which silently omits anything matched by
|
|
703
|
+
`.gitignore`. If your `testCommand` (or the build it triggers) writes
|
|
704
|
+
tracked-but-untracked output — a `dist/`, a coverage report, a log file — into
|
|
705
|
+
the working tree, the tree never goes clean after a green test run, and the
|
|
706
|
+
fixpoint loop cannot converge: it will either loop forever re-detecting a
|
|
707
|
+
"dirty" boundary or misclassify build output as the human's next feature
|
|
708
|
+
capture. Gitignore every path your test/build toolchain writes before wiring
|
|
709
|
+
gtd into a repo.
|
|
710
|
+
- **Repository root invocation.** Every subcommand except `--help`/`--version`
|
|
711
|
+
must run from the git repository root — steering files and diffs are resolved
|
|
712
|
+
against the process cwd.
|
|
652
713
|
|
|
653
|
-
|
|
714
|
+
## Build orchestration
|
|
654
715
|
|
|
655
716
|
### Decompose
|
|
656
717
|
|
|
657
|
-
The Grilled
|
|
658
|
-
|
|
718
|
+
The Grilled/Planning states spawn a planning-model subagent that breaks
|
|
719
|
+
`.gtd/TODO.md` into executable work packages under `.gtd/`:
|
|
659
720
|
|
|
660
721
|
```
|
|
661
722
|
.gtd/
|
|
@@ -680,171 +741,64 @@ Rules:
|
|
|
680
741
|
- **Task files are self-contained** — description, acceptance-criteria
|
|
681
742
|
checkboxes, relevant file paths, constraints, and edge cases.
|
|
682
743
|
|
|
683
|
-
Packages carry only their task `.md` files; the edge commits each built package
|
|
684
|
-
`gtd: building`.
|
|
685
|
-
|
|
686
744
|
### Execute
|
|
687
745
|
|
|
688
|
-
Execution is **one package per cycle**. gtd selects the single next
|
|
689
|
-
itself, names it in the prompt, and inlines its task files' full
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
1. Spawn parallel execution-model workers for all tasks in the selected package
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
**Recommendation:** 25 per page — matches the admin tables elsewhere.
|
|
745
|
-
|
|
746
|
-
**Answer:** 50 — these tables get long and 25 wastes a click for most users.
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
When there are genuinely no open questions left, the agent writes the sentinel
|
|
750
|
-
line `no open questions — run gtd to plan` and leaves **no** markers — a clean
|
|
751
|
-
tree with no markers is what advances the plan to **Grilled** and decomposition.
|
|
752
|
-
|
|
753
|
-
## CLI flags
|
|
754
|
-
|
|
755
|
-
```
|
|
756
|
-
Usage: gtd [command] [options]
|
|
757
|
-
|
|
758
|
-
Commands:
|
|
759
|
-
(default) Run the gtd driver loop — detect state, emit next prompt
|
|
760
|
-
format <file> Format a markdown file in place
|
|
761
|
-
review <target> Ad-hoc human review against a git ref or branch
|
|
762
|
-
|
|
763
|
-
Options:
|
|
764
|
-
--json Output structured JSON instead of plain text
|
|
765
|
-
--verbose Show verbose output (thinking deltas, tool events)
|
|
766
|
-
--debug Show debug-level internal information
|
|
767
|
-
--version, -v Print version and exit
|
|
768
|
-
--help, -h Print this help and exit
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
`--version` (`-v`) and `--help` (`-h`) short-circuit before any git or
|
|
772
|
-
repository-state work — they run outside a repo and in any repo state.
|
|
773
|
-
|
|
774
|
-
## Subcommands
|
|
775
|
-
|
|
776
|
-
gtd ships two subcommands: `format` and `review`.
|
|
777
|
-
|
|
778
|
-
## Review subcommand
|
|
779
|
-
|
|
780
|
-
```bash
|
|
781
|
-
gtd review <target>
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
Starts an explicit, on-demand human review of the diff between HEAD and
|
|
785
|
-
`merge-base(<target>, HEAD)`. Use this when the automatic review base yields no
|
|
786
|
-
diff (idle tree outside a process) or when you want to review against a specific
|
|
787
|
-
base regardless of workflow state.
|
|
788
|
-
|
|
789
|
-
### Flow
|
|
790
|
-
|
|
791
|
-
1. `gtd review <target>` computes the diff HEAD adds over
|
|
792
|
-
`merge-base(<target>, HEAD)`.
|
|
793
|
-
2. The edge writes an empty anchor commit `gtd: reviewing` (no content — just
|
|
794
|
-
the marker).
|
|
795
|
-
3. The **Clean** state writes `REVIEW.md` with the computed diff and emits the
|
|
796
|
-
normal review prompt. `--json` is accepted and enables auto-advance mode
|
|
797
|
-
(same behavior as the default command).
|
|
798
|
-
4. The normal loop then drives: **Await Review** → **Done** → **Squashing**,
|
|
799
|
-
collapsing back to the `gtd: reviewing` anchor commit.
|
|
800
|
-
|
|
801
|
-
### Error handling
|
|
802
|
-
|
|
803
|
-
All errors exit with **code 1** and write a message to **stderr**:
|
|
804
|
-
|
|
805
|
-
- **Missing target** — `gtd review` with no argument:
|
|
806
|
-
`gtd review: missing target argument`
|
|
807
|
-
- **Extra arguments** — `gtd review main extra`:
|
|
808
|
-
`gtd review: too many arguments — expected one target, got: …`
|
|
809
|
-
- **Unresolvable ref** — the target cannot be resolved by git:
|
|
810
|
-
`gtd review: cannot resolve ref '<target>': <error message>`
|
|
811
|
-
- **Empty diff** — the merge-base diff between `<target>` and HEAD is empty
|
|
812
|
-
(nothing to review):
|
|
813
|
-
`gtd review: nothing to review (<target> diff is empty after filtering)`
|
|
814
|
-
|
|
815
|
-
## Format subcommand
|
|
816
|
-
|
|
817
|
-
`gtd format` formats a markdown file in place:
|
|
818
|
-
|
|
819
|
-
```bash
|
|
820
|
-
gtd format <file>
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
It uses a bundled prettier with a fixed, gtd-owned config (`parser: "markdown"`,
|
|
824
|
-
`printWidth: 80`, `proseWrap: "always"`). The host repo's `.prettierrc` is
|
|
825
|
-
**intentionally ignored** — determinism across consumer repos matters more than
|
|
826
|
-
local style preferences.
|
|
827
|
-
|
|
828
|
-
The grilling and clean prompts instruct the agent to run this command after
|
|
829
|
-
every edit to `TODO.md` or `REVIEW.md`, so those files stay consistently
|
|
830
|
-
formatted regardless of the host project's toolchain.
|
|
831
|
-
|
|
832
|
-
### Error handling
|
|
833
|
-
|
|
834
|
-
All errors exit with **code 1** and write a message to **stderr**:
|
|
835
|
-
|
|
836
|
-
- **Missing path** — `gtd format` with no argument:
|
|
837
|
-
`gtd format: missing file path argument`
|
|
838
|
-
- **Extra arguments** — `gtd format a.md b.md`:
|
|
839
|
-
`gtd format: too many arguments — expected one path, got: …`
|
|
840
|
-
- **Non-markdown file** — any extension other than `.md` or `.markdown`
|
|
841
|
-
(case-insensitive):
|
|
842
|
-
`gtd format: <file> is not a markdown file (expected .md or .markdown)`
|
|
843
|
-
- **File not found** — the path does not exist:
|
|
844
|
-
`gtd: skipped formatting <file>: not found`
|
|
845
|
-
|
|
846
|
-
> [!NOTE] Upgrading gtd may reflow existing `TODO.md` files if the bundled
|
|
847
|
-
> prettier major version changes.
|
|
746
|
+
Execution is **one package per cycle**. `gtd next` selects the single next
|
|
747
|
+
package itself, names it in the prompt, and inlines its task files' full
|
|
748
|
+
contents — the agent never browses `.gtd/` or picks a package itself. A single
|
|
749
|
+
cycle:
|
|
750
|
+
|
|
751
|
+
1. Spawn parallel execution-model workers for all tasks in the selected package.
|
|
752
|
+
2. Leave all changes **uncommitted**. Do not commit, do not delete the package
|
|
753
|
+
directory, do not run tests.
|
|
754
|
+
3. Finish the turn with `gtd step-agent` — the next hop's edge action commits
|
|
755
|
+
the work (`gtd(agent): building`) and runs `testCommand` to verify it.
|
|
756
|
+
|
|
757
|
+
## Upgrading from v1 (BREAKING CHANGE)
|
|
758
|
+
|
|
759
|
+
v2 ships as a **major** semantic-release bump (`2.0.0`) so the binary and the
|
|
760
|
+
loop-driving text (this README, `skills/loop/SKILL.md`) can never skew against
|
|
761
|
+
each other. There is **no backward compatibility with the v1 command surface**:
|
|
762
|
+
the single mutating `gtd` command, marker/sentinel files, the `autoAdvance` JSON
|
|
763
|
+
field, and the `gtd: transport` handoff commit are all gone. `gtd` bare now
|
|
764
|
+
errors rather than driving a loop; use `gtd step-agent` / `gtd next` /
|
|
765
|
+
`gtd step` instead.
|
|
766
|
+
|
|
767
|
+
**Commit-history compatibility is one-way.** Any repo with v1-taxonomy history
|
|
768
|
+
in it (`gtd: new task`, `gtd: grilling`, `gtd: transport`, a bare
|
|
769
|
+
`gtd: reviewing` with no hash, …) upgrades cleanly: those subjects fall outside
|
|
770
|
+
v2's closed turn/routing grammar and parse as inert **boundary commits** — they
|
|
771
|
+
are never mistaken for v2 workflow state and never error.
|
|
772
|
+
|
|
773
|
+
**Finish or clean up any in-flight v1 cycle first.** If a repo has an
|
|
774
|
+
**in-progress** v1 cycle — steering files present (root-level `TODO.md`,
|
|
775
|
+
`REVIEW.md`, `FEEDBACK.md`, `ERRORS.md`, or `.gtd/`) whose HEAD carries v1-only
|
|
776
|
+
commit subjects — the v2 binary does not know how to resume it: v1 steering
|
|
777
|
+
files have no v2 turn commit backing them, so a cold v2 invocation on that tree
|
|
778
|
+
can land in an unrecognized state. Either finish the v1 cycle to a clean
|
|
779
|
+
boundary with your existing v1 binary before upgrading, or manually clean up
|
|
780
|
+
(remove the steering files / `.gtd/`, commit the result) so the upgrade starts
|
|
781
|
+
from a plain boundary HEAD.
|
|
782
|
+
|
|
783
|
+
**Steering files moved into `.gtd/`.** Earlier v2 builds kept `TODO.md`,
|
|
784
|
+
`REVIEW.md`, `FEEDBACK.md`, `ERRORS.md`, `HEALTH.md`, and `SQUASH_MSG.md` at the
|
|
785
|
+
repository root; they now live under `.gtd/`. Upgrade at a clean boundary (idle,
|
|
786
|
+
post-squash): a repo at rest needs nothing. Mid-cycle repos should either finish
|
|
787
|
+
the cycle on the old build first or move the root-level steering files into
|
|
788
|
+
`.gtd/` by hand and commit. History classification is backward-compatible — the
|
|
789
|
+
counter folds recognize both the old root paths and the new `.gtd/` paths in
|
|
790
|
+
existing commits.
|
|
791
|
+
|
|
792
|
+
**Re-copy the loop skill.** If you vendor `skills/loop/` into a consuming repo
|
|
793
|
+
or agent harness, upgrading the `gtd` binary also means re-copying that skill
|
|
794
|
+
from this release — the v1 skill text still describes the old single-command
|
|
795
|
+
loop and will drive the new binary incorrectly.
|
|
796
|
+
|
|
797
|
+
For maintainers: this repo releases via `semantic-release` reading Conventional
|
|
798
|
+
Commits, and needs **no config change** for a major bump — but the release
|
|
799
|
+
commit/PR **must carry a `BREAKING CHANGE:` footer** (or a `!` after the type)
|
|
800
|
+
for `@semantic-release/commit-analyzer` to compute `2.0.0` rather than a
|
|
801
|
+
minor/patch bump.
|
|
848
802
|
|
|
849
803
|
## Development
|
|
850
804
|
|
|
@@ -852,9 +806,10 @@ All errors exit with **code 1** and write a message to **stderr**:
|
|
|
852
806
|
npm install
|
|
853
807
|
npm run dev # run from source, no build (node dev/run.mjs)
|
|
854
808
|
npm run build # tsup → dist/gtd.bundle.mjs
|
|
855
|
-
npm test #
|
|
809
|
+
npm test # format:check, typecheck, lint, unit + e2e tests, fallow
|
|
810
|
+
npm run test:unit # vitest unit tests (the pure resolver) — --project unit
|
|
856
811
|
npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
|
|
857
|
-
npm run test:mutation # StrykerJS mutation testing
|
|
812
|
+
npm run test:mutation # StrykerJS mutation testing (manual only, ~2 min)
|
|
858
813
|
npm run typecheck
|
|
859
814
|
npm run lint
|
|
860
815
|
```
|
|
@@ -862,29 +817,20 @@ npm run lint
|
|
|
862
817
|
### Pre-commit hook
|
|
863
818
|
|
|
864
819
|
A pre-commit hook is installed automatically via the `prepare` script when you
|
|
865
|
-
run `npm install` on a fresh clone — no manual setup needed.
|
|
866
|
-
|
|
867
|
-
The hook runs [lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
820
|
+
run `npm install` on a fresh clone — no manual setup needed. The hook runs
|
|
821
|
+
[lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
868
822
|
[Prettier](https://prettier.io/), formatting every staged file before each
|
|
869
|
-
commit:
|
|
870
|
-
|
|
871
|
-
```
|
|
872
|
-
prettier --ignore-unknown --write
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
This mirrors the `format:check` step enforced in CI (`prettier --check .`),
|
|
876
|
-
keeping committed code consistently formatted without requiring a separate
|
|
877
|
-
manual format pass.
|
|
823
|
+
commit (`prettier --ignore-unknown --write`), mirroring the `format:check` step
|
|
824
|
+
enforced in CI (`prettier --check .`).
|
|
878
825
|
|
|
879
826
|
### Prompt templates
|
|
880
827
|
|
|
881
828
|
Each prompt-bearing state has a self-contained Eta template in
|
|
882
|
-
`src/prompts/*.md` that owns its full prompt — header, context,
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
model string are injected as Eta variables (`<%= model %>`).
|
|
829
|
+
`src/prompts/*.md` that owns its full prompt — header, context, and body. Shared
|
|
830
|
+
fragments live as partials in `src/prompts/partials/`: `header`, the context
|
|
831
|
+
renderers (`diff`, `feedback`, `package`), and the single `agent-turn` tail
|
|
832
|
+
partial (the pinned "Finish your turn by running `gtd step-agent`. Then run
|
|
833
|
+
`gtd next` …" loop-closing instructions).
|
|
888
834
|
|
|
889
835
|
At module load, `src/Prompt.ts` registers every template on a single `new Eta()`
|
|
890
836
|
instance via `loadTemplate`. `readFile` and `resolvePath` are nulled afterward
|
|
@@ -894,16 +840,17 @@ bundle carries no runtime `fs` dependency.
|
|
|
894
840
|
`buildPrompt(result, resolveModel?, output?)` selects the state's template,
|
|
895
841
|
builds a view-model (model string, tail partial name, context), renders it,
|
|
896
842
|
collapses runs of three or more blank lines to two, and ensures exactly one
|
|
897
|
-
trailing newline.
|
|
843
|
+
trailing newline. It throws for the five states that render no prompt at all
|
|
844
|
+
(`testing`, `planning`, `close-package`, `done`, `health-check`) — those are
|
|
845
|
+
performed entirely by the edge.
|
|
898
846
|
|
|
899
847
|
`npm run dev` runs `src/main.ts` directly via Node's native TypeScript
|
|
900
848
|
type-stripping (requires Node 22.6+). It registers `dev/hooks.mjs`, which fills
|
|
901
849
|
the two gaps the tsup build otherwise covers: resolving `./Foo.js` specifiers to
|
|
902
850
|
the on-disk `./Foo.ts`, and importing `*.md` prompt files as text. Pass CLI args
|
|
903
|
-
after `--`, e.g. `npm run dev -- format <file>`.
|
|
904
|
-
rather than `scripts/` because tsup wipes `dist/` (`clean: true`) on build.
|
|
851
|
+
after `--`, e.g. `npm run dev -- format <file>`.
|
|
905
852
|
|
|
906
|
-
The decision core (`src/Machine.ts`) is pure and IO-free, so the whole
|
|
853
|
+
The decision core (`src/Machine.ts`) is pure and IO-free, so the whole 16-state
|
|
907
854
|
ladder and both counter folds are trivially unit-testable in isolation; all
|
|
908
855
|
git/filesystem IO is confined to the edge (`src/Events.ts`).
|
|
909
856
|
|
|
@@ -912,8 +859,10 @@ binary via the `bin` field in `package.json`.
|
|
|
912
859
|
|
|
913
860
|
### Mutation testing
|
|
914
861
|
|
|
915
|
-
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min)
|
|
916
|
-
|
|
862
|
+
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min)
|
|
863
|
+
— never run it as part of routine development; it is a deliberate,
|
|
864
|
+
manually-triggered check. The single `stryker.config.json` mutates six core
|
|
865
|
+
files:
|
|
917
866
|
|
|
918
867
|
```
|
|
919
868
|
src/Machine.ts src/Prompt.ts src/Config.ts
|
|
@@ -921,56 +870,9 @@ src/Format.ts src/State.ts src/Events.ts
|
|
|
921
870
|
```
|
|
922
871
|
|
|
923
872
|
`src/Git.ts` is excluded: the Cucumber harness stubs git at the Effect boundary,
|
|
924
|
-
so Git.ts mutants have zero in-memory coverage.
|
|
925
|
-
Live-tier kill rate is a follow-up before re-including it.
|
|
926
|
-
|
|
927
|
-
**`process.chdir()` gotcha (resolved).** `@stryker-mutator/vitest-runner`
|
|
928
|
-
hardcodes `pool: 'threads'` internally, and `process.chdir()` is unsupported in
|
|
929
|
-
worker threads. Before the cwd refactor (package 01), four test files
|
|
930
|
-
(`Events.test.ts`, `Git.test.ts`, `Config.test.ts`, `TestRunner.test.ts`) had to
|
|
931
|
-
be excluded from all Stryker runs. The refactor eliminated those calls, letting
|
|
932
|
-
all four files rejoin the run.
|
|
933
|
-
|
|
934
|
-
Two additional notes: `vitest.related` is disabled for feature-file runs because
|
|
935
|
-
feature files don't import source files directly (Stryker's coverage-based
|
|
936
|
-
filtering would assign zero tests to every mutant). Compile-error mutants are
|
|
937
|
-
counted as kills by the TypeScript checker — they represent real signal, not a
|
|
938
|
-
configuration problem.
|
|
939
|
-
|
|
940
|
-
Run `npm run test:mutation` after making changes to the mutated files to check
|
|
941
|
-
whether surviving mutants increased. The HTML report lands in
|
|
942
|
-
`reports/mutation/mutation.html` (git-ignored).
|
|
943
|
-
|
|
944
|
-
### Mutation testing
|
|
945
|
-
|
|
946
|
-
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min).
|
|
947
|
-
The single `stryker.config.json` mutates six core files:
|
|
948
|
-
|
|
949
|
-
```
|
|
950
|
-
src/Machine.ts src/Prompt.ts src/Config.ts
|
|
951
|
-
src/Format.ts src/State.ts src/Events.ts
|
|
952
|
-
```
|
|
873
|
+
so `Git.ts` mutants have zero in-memory coverage.
|
|
953
874
|
|
|
954
|
-
|
|
955
|
-
so Git.ts mutants have zero in-memory coverage. Measuring its post-refactor
|
|
956
|
-
Live-tier kill rate is a follow-up before re-including it.
|
|
957
|
-
|
|
958
|
-
**`process.chdir()` gotcha (resolved).** `@stryker-mutator/vitest-runner`
|
|
959
|
-
hardcodes `pool: 'threads'` internally, and `process.chdir()` is unsupported in
|
|
960
|
-
worker threads. Before the cwd refactor (package 01), four test files
|
|
961
|
-
(`Events.test.ts`, `Git.test.ts`, `Config.test.ts`, `TestRunner.test.ts`) had to
|
|
962
|
-
be excluded from all Stryker runs. The refactor eliminated those calls, letting
|
|
963
|
-
all four files rejoin the run.
|
|
964
|
-
|
|
965
|
-
Two additional notes: `vitest.related` is disabled for feature-file runs because
|
|
966
|
-
feature files don't import source files directly (Stryker's coverage-based
|
|
967
|
-
filtering would assign zero tests to every mutant). Compile-error mutants are
|
|
968
|
-
counted as kills by the TypeScript checker — they represent real signal, not a
|
|
969
|
-
configuration problem.
|
|
970
|
-
|
|
971
|
-
Run `npm run test:mutation` after making changes to the mutated files to check
|
|
972
|
-
whether surviving mutants increased. The HTML report lands in
|
|
973
|
-
`reports/mutation/mutation.html` (git-ignored).
|
|
875
|
+
The HTML report lands in `reports/mutation/mutation.html` (git-ignored).
|
|
974
876
|
|
|
975
877
|
## Releasing
|
|
976
878
|
|