@pmelab/gtd 1.4.3
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/LICENSE +21 -0
- package/README.md +800 -0
- package/dist/gtd.bundle.mjs +388717 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
# gi[t]hings.**done**
|
|
2
|
+
|
|
3
|
+
> [!WARNING] This project is an experiment in unapologetic vibe coding. Code
|
|
4
|
+
> might be terrible, I don't even know 🤷♂️ But otherwise I wouldn't have built it
|
|
5
|
+
> in the first place. Now I have something that actually helps me.
|
|
6
|
+
|
|
7
|
+
A git-aware CLI that emits the next prompt for an autonomous coding agent based
|
|
8
|
+
on the current repository state — capture an idea, grill it into a plan,
|
|
9
|
+
decompose it into work packages, execute with parallel subagents, test,
|
|
10
|
+
agentically review each package, and finally walk a human through a review.
|
|
11
|
+
|
|
12
|
+
Internally, gtd is a **pure fold** over git history. The decision core
|
|
13
|
+
(`src/Machine.ts`) is a single IO-free function, `resolve(events)` — **no
|
|
14
|
+
xstate, no actor, no Effect**. The Effect "edge" (`src/Events.ts`) does all the
|
|
15
|
+
git/filesystem IO: it reads the **first-parent** commit subjects since the
|
|
16
|
+
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
|
+
— i.e. budgets engage on the default branch too) plus the working tree, turns
|
|
19
|
+
them into a `COMMIT[]` + single terminal `RESOLVE` event stream, and folds them
|
|
20
|
+
through the machine. The fold lands on exactly **one** of 17 states, which
|
|
21
|
+
selects the prompt. A single run resolves to a single state.
|
|
22
|
+
|
|
23
|
+
`resolve()` returns that state plus an optional **`EdgeAction`** (a commit,
|
|
24
|
+
revert, test run, or file write). The driver loop (`src/main.ts`) performs the
|
|
25
|
+
action, then re-gathers and re-resolves — auto-advancing through the
|
|
26
|
+
deterministic chain **within one invocation** until it reaches a prompt-bearing
|
|
27
|
+
or STOP state. The agent never runs `git commit` itself: every agent leaves its
|
|
28
|
+
output **uncommitted**, and the edge commits it with the right flat
|
|
29
|
+
`gtd: <phase>` subject on the next hop. The machine stays pure — it only decides
|
|
30
|
+
_which_ action; the semantics live in the edge.
|
|
31
|
+
|
|
32
|
+
`gtd` is an npm CLI — install it, run `gtd` in a repo, and it prints the next
|
|
33
|
+
prompt to stdout; a human or any agent reads and follows it.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @pmelab/gtd
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or run without installing:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @pmelab/gtd
|
|
45
|
+
```
|
|
46
|
+
|
|
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.
|
|
54
|
+
|
|
55
|
+
## JSON output mode
|
|
56
|
+
|
|
57
|
+
Pass `--json` to the default `gtd` invocation to receive machine-readable output
|
|
58
|
+
instead of a plain prompt:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gtd --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`--json` applies only to the default command. Passing it to `gtd format` is
|
|
65
|
+
rejected with exit code 1 and an error on stderr. It is orthogonal to all other
|
|
66
|
+
flags (`--verbose`, `--debug`, etc.) — each controls exactly one concern.
|
|
67
|
+
|
|
68
|
+
### Output shape
|
|
69
|
+
|
|
70
|
+
In `--json` mode gtd emits a **single-line JSON object** to stdout:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{ "state": "building", "autoAdvance": true, "prompt": "..." }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- **`state`** — the resolved prompt-bearing `GtdState` (e.g. `"grilling"`,
|
|
77
|
+
`"building"`, `"fixing"`, `"clean"`).
|
|
78
|
+
- **`autoAdvance`** — the same boolean that selects the loop-tail in plain mode.
|
|
79
|
+
`true` means the workflow advances automatically after the agent acts; `false`
|
|
80
|
+
means a STOP state was reached and human input is expected.
|
|
81
|
+
- **`prompt`** — the full markdown prompt, but with **both loop-control tails
|
|
82
|
+
omitted**. In their place, the prompt ends with:
|
|
83
|
+
`Complete the steps above, then end your turn — the harness decides what happens next.`
|
|
84
|
+
The caller is responsible for reading `autoAdvance` and deciding whether to
|
|
85
|
+
run another cycle.
|
|
86
|
+
|
|
87
|
+
### Loop-ownership division of labor
|
|
88
|
+
|
|
89
|
+
In **plain mode** the in-prompt tails own the loop — the prompt instructs the
|
|
90
|
+
agent to re-run `gtd` when `autoAdvance` is true.
|
|
91
|
+
|
|
92
|
+
In **`--json` mode** the **caller owns the loop** — the tails are stripped and
|
|
93
|
+
the caller reads `autoAdvance` from the JSON object to decide whether to
|
|
94
|
+
iterate.
|
|
95
|
+
|
|
96
|
+
Example driver script:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
#!/usr/bin/env bash
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
|
|
102
|
+
while true; do
|
|
103
|
+
out="$(gtd --json)"
|
|
104
|
+
prompt="$(jq -r .prompt <<<"$out")"
|
|
105
|
+
claude -p "$prompt" --dangerously-skip-permissions
|
|
106
|
+
jq -e .autoAdvance <<<"$out" >/dev/null || break
|
|
107
|
+
done
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Error behavior
|
|
111
|
+
|
|
112
|
+
Errors are reported **inside** the JSON object rather than as unstructured text:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "state": "error", "autoAdvance": false, "prompt": "<message>" }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The process still exits with code 1. Exit codes are otherwise unchanged: 0 on
|
|
119
|
+
success, 1 on error.
|
|
120
|
+
|
|
121
|
+
## Steering files
|
|
122
|
+
|
|
123
|
+
`gtd` writes and commits temporary steering files that carry workflow state
|
|
124
|
+
across runs:
|
|
125
|
+
|
|
126
|
+
- **TODO.md** — the current plan, under development during grilling.
|
|
127
|
+
- **REVIEW.md** — a guided human review spanning a commit diff. Format:
|
|
128
|
+
- `# Review: <short-hash>` heading + `<!-- base: <full-hash> -->` marker
|
|
129
|
+
identifying the review base commit
|
|
130
|
+
- Per-hunk `- [ ]` checkboxes: ticking them (`- [ ]` → `- [x]`) is the
|
|
131
|
+
**approval signal** — checkbox-only edits route to Done; _unchecked_ boxes
|
|
132
|
+
never gate the workflow
|
|
133
|
+
- Open questions at the top, resolved/addressed items at the bottom
|
|
134
|
+
(consistent with TODO.md grilling convention)
|
|
135
|
+
- **FEEDBACK.md** — test-failure output, **or** agentic-review findings, to be
|
|
136
|
+
fixed. An **empty** FEEDBACK.md from a clean agentic review signals
|
|
137
|
+
**approval** (→ Close package).
|
|
138
|
+
- **ERRORS.md** — the escalation gate: persistent test-failure output that stops
|
|
139
|
+
the loop for a human (written instead of FEEDBACK.md once the fix-attempt cap
|
|
140
|
+
is hit; never auto-consumed).
|
|
141
|
+
- **.gtd/** — ordered work packages (one numbered directory each) of
|
|
142
|
+
parallelizable subtasks.
|
|
143
|
+
|
|
144
|
+
Steering files are **authoritative**: while any exist, `gtd` resumes that
|
|
145
|
+
workflow regardless of the last commit (even a non-gtd one). They are **never
|
|
146
|
+
garbage-collected automatically** — a stale steering file from an abandoned
|
|
147
|
+
branch is resumed exactly like a live one, so you must `rm` files from a
|
|
148
|
+
workflow you have abandoned.
|
|
149
|
+
|
|
150
|
+
"**Code changes**" below means pending working-tree changes (tracked or
|
|
151
|
+
untracked, respecting `.gitignore`) **outside** the steering set. Changes to
|
|
152
|
+
steering files are detected separately.
|
|
153
|
+
|
|
154
|
+
## Detection model
|
|
155
|
+
|
|
156
|
+
Every run derives the state in **three layers**:
|
|
157
|
+
|
|
158
|
+
1. **Transport pre-pass** — if HEAD is `gtd: transport`, short-circuit to the
|
|
159
|
+
Transport state (mixed-reset) before anything else is considered.
|
|
160
|
+
2. **Steering-file precedence** — the presence of `ERRORS.md` / `FEEDBACK.md` /
|
|
161
|
+
`.gtd/` / `REVIEW.md` drives the decision, authoritative regardless of HEAD.
|
|
162
|
+
3. **HEAD bucket** — with no steering files in play, the last-commit bucket plus
|
|
163
|
+
working-tree cleanliness selects New Feature / Grilling / Clean / Idle.
|
|
164
|
+
|
|
165
|
+
Within layers 2 and 3 the HEAD subject further disambiguates states the
|
|
166
|
+
filesystem alone cannot separate (e.g. inside the `.gtd/` lifecycle, HEAD
|
|
167
|
+
`gtd: planning` vs `gtd: building` vs `gtd: package done`).
|
|
168
|
+
|
|
169
|
+
### Commit taxonomy
|
|
170
|
+
|
|
171
|
+
`gtd` writes a single **flat** `gtd: <phase>` subject for every workflow commit.
|
|
172
|
+
The complete set:
|
|
173
|
+
|
|
174
|
+
`gtd: new task` · `gtd: grilling` · `gtd: grilled` · `gtd: planning` ·
|
|
175
|
+
`gtd: building` · `gtd: errors` · `gtd: feedback` · `gtd: fixing` ·
|
|
176
|
+
`gtd: package done` · `gtd: awaiting review` · `gtd: done` — plus the hand-made
|
|
177
|
+
`gtd: transport` (see below).
|
|
178
|
+
|
|
179
|
+
The last commit subject is bucketed two ways:
|
|
180
|
+
|
|
181
|
+
- **Boundary** — a non-`gtd:` commit, or exactly `gtd: done`. Marks a cold
|
|
182
|
+
start: no workflow in progress.
|
|
183
|
+
- **Mid-phase** — any other `gtd: <phase>` subject. Identifies the exact phase
|
|
184
|
+
of an in-progress workflow.
|
|
185
|
+
|
|
186
|
+
### Precedence ladder (first match wins)
|
|
187
|
+
|
|
188
|
+
0. **HEAD `gtd: transport`** → Transport.
|
|
189
|
+
1. **ERRORS.md present** → Escalate (human gate; STOP).
|
|
190
|
+
2. **FEEDBACK.md present** → non-empty → Fixing; **empty** (clean agentic review
|
|
191
|
+
= approval) → Close package.
|
|
192
|
+
3. **.gtd present** → build lifecycle, routed by tree + HEAD:
|
|
193
|
+
- `.gtd` modified (package files added/edited) → Planning
|
|
194
|
+
- code changes present → Testing
|
|
195
|
+
- clean tree + HEAD `gtd: fixing` (no-op fixer) → Testing (re-test)
|
|
196
|
+
- else clean, by HEAD: `gtd: planning` / `gtd: package done` → Building;
|
|
197
|
+
`gtd: building` → Agentic Review (or Close package, if force-approved)
|
|
198
|
+
4. **REVIEW.md present** → review lifecycle, routed by committed-ness + tree:
|
|
199
|
+
committed + clean → Done; committed + checkbox-only edits (only `[ ]`↔`[x]`
|
|
200
|
+
flips in REVIEW.md) → Done; committed + non-checkbox pending edits → Accept
|
|
201
|
+
Review; uncommitted → Await Review (commits REVIEW.md and auto-advances to
|
|
202
|
+
Done). 4a. **HEAD `gtd: done` + `squash` enabled + squash base present + no
|
|
203
|
+
unrelated code dirty** (a lone untracked `SQUASH_MSG.md` is allowed) →
|
|
204
|
+
Squashing; unrelated code dirty → New Feature.
|
|
205
|
+
5. **Boundary HEAD + pending changes** (and no `.gtd`/REVIEW/FEEDBACK), or HEAD
|
|
206
|
+
`gtd: new task` + clean tree (regenerate a lost seed) → New Feature.
|
|
207
|
+
6. **TODO.md present** → Grilling / Grilled.
|
|
208
|
+
7. **Boundary or `gtd: package done` HEAD + clean tree** → Clean (review the
|
|
209
|
+
work) or Idle (nothing to review).
|
|
210
|
+
|
|
211
|
+
Anything matching no rule is corruption — `gtd` **hard-errors** rather than
|
|
212
|
+
guess.
|
|
213
|
+
|
|
214
|
+
```mermaid
|
|
215
|
+
flowchart TD
|
|
216
|
+
Start([Run gtd]) --> P0{"HEAD = gtd: transport?"}
|
|
217
|
+
P0 -->|yes| Transport["Transport — mixed-reset HEAD, re-derive"]:::edge
|
|
218
|
+
Transport -.->|re-resolve| Start
|
|
219
|
+
P0 -->|no| P1{"ERRORS.md?"}
|
|
220
|
+
P1 -->|yes| Escalate["Escalate — STOP, human gate"]:::gate
|
|
221
|
+
P1 -->|no| P2{"FEEDBACK.md?"}
|
|
222
|
+
P2 -->|"empty = approval"| Close["Close package — rm pkg dir, gtd: package done"]:::edge
|
|
223
|
+
P2 -->|"non-empty"| Fixing["Fixing — rm FEEDBACK, fixer agent"]:::agent
|
|
224
|
+
P2 -->|absent| P3{".gtd/?"}
|
|
225
|
+
P3 -->|"modified"| Planning["Planning — gtd: planning"]:::agent
|
|
226
|
+
P3 -->|"code dirty / resume / no-op fixer"| Testing["Testing — gtd: building, run tests"]:::edge
|
|
227
|
+
P3 -->|"clean, HEAD planning/package done"| Building["Building — pick & build one package"]:::agent
|
|
228
|
+
P3 -->|"clean, HEAD building"| Review["Agentic Review — write FEEDBACK.md"]:::agent
|
|
229
|
+
P3 -->|absent| P4{"REVIEW.md?"}
|
|
230
|
+
P4 -->|"committed + clean or checkbox-only edits"| Done["Done — rm REVIEW, gtd: done"]:::edge
|
|
231
|
+
P4 -->|"committed + non-checkbox edits"| Accept["Accept Review — seed TODO, checkout, rm REVIEW"]:::edge
|
|
232
|
+
P4 -->|"uncommitted"| Await["Await Review — commit gtd: awaiting review"]:::edge
|
|
233
|
+
Await -.->|"re-resolve"| Done
|
|
234
|
+
P4 -->|absent| P5{"boundary HEAD + dirty,<br/>or gtd: new task + clean?"}
|
|
235
|
+
Done -->|"squash enabled"| Squashing["Squashing — reset --soft base, squash commit"]:::agent
|
|
236
|
+
Done -->|"squash disabled"| Idle
|
|
237
|
+
Squashing --> Idle["Idle — nothing to do (STOP; review on next manual gtd run)"]:::gate
|
|
238
|
+
P5 -->|yes| NewFeature["New Feature — gtd: new task, revert, seed TODO"]:::edge
|
|
239
|
+
P5 -->|no| P6{"TODO.md?"}
|
|
240
|
+
P6 -->|"open markers"| GrillStop["Grilling — gtd: grilling, STOP for answers"]:::gate
|
|
241
|
+
P6 -->|"dirty, no markers"| GrillIter["Grilling — gtd: grilling, agent iterates"]:::agent
|
|
242
|
+
P6 -->|"clean, no markers"| Grilled["Grilled — gtd: grilled, decompose"]:::agent
|
|
243
|
+
P6 -->|absent| P7{"clean + boundary/package-done HEAD,<br/>reviewable diff?"}
|
|
244
|
+
P7 -->|yes| CleanState["Clean — write REVIEW.md"]:::agent
|
|
245
|
+
P7 -->|no| Idle
|
|
246
|
+
classDef edge fill:#1a4a6b,color:#fff
|
|
247
|
+
classDef agent fill:#2d6a4f,color:#fff
|
|
248
|
+
classDef gate fill:#7a3b1d,color:#fff
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
> Blue = **edge-only** (the edge performs IO; no prompt rendered). Green =
|
|
252
|
+
> **agent** (a prompt is emitted; the agent acts, then re-runs gtd). Brown =
|
|
253
|
+
> **gate** (STOP for the human, or nothing to do).
|
|
254
|
+
|
|
255
|
+
### Illegal combinations
|
|
256
|
+
|
|
257
|
+
These never arise in normal flow; if seen, `gtd` hard-errors rather than
|
|
258
|
+
guessing:
|
|
259
|
+
|
|
260
|
+
- REVIEW.md + .gtd
|
|
261
|
+
- REVIEW.md + TODO.md
|
|
262
|
+
- FEEDBACK.md + REVIEW.md
|
|
263
|
+
- FEEDBACK.md without .gtd
|
|
264
|
+
- ERRORS.md + FEEDBACK.md
|
|
265
|
+
- ERRORS.md without .gtd
|
|
266
|
+
|
|
267
|
+
Legal coexistence: `.gtd` + TODO.md (plan kept alongside packages during
|
|
268
|
+
**Planning** only — TODO.md is deleted at the first Building turn);
|
|
269
|
+
FEEDBACK.md + `.gtd` (a fix during build).
|
|
270
|
+
|
|
271
|
+
### Single writer, linear branch
|
|
272
|
+
|
|
273
|
+
State is folded from **first-parent** history: gtd assumes a **single writer on
|
|
274
|
+
a linear branch**. A merge commit at HEAD is unsupported — it breaks the counter
|
|
275
|
+
folds, the review base, and last-commit detection (documented, not handled).
|
|
276
|
+
|
|
277
|
+
Distribute work by **sequential handoff** (one active machine at a time) over
|
|
278
|
+
**rebase / fast-forward**, not by merging parallel branches. The primitive for
|
|
279
|
+
carrying _uncommitted_ work across machines or branches is `gtd: transport`:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git add -A && git commit -m "gtd: transport" # on the source machine
|
|
283
|
+
git push # … then pull on the far side
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
There is **no `gtd transport` subcommand** — you make this commit by hand. The
|
|
287
|
+
**Transport** state consumes it: on the far side, the next `gtd` run sees the
|
|
288
|
+
`gtd: transport` HEAD, mixed-resets it (`git reset HEAD~1`) to drop the work
|
|
289
|
+
back into the working tree uncommitted, and re-derives state from scratch. If
|
|
290
|
+
the transport commit is the repository's root commit (no parent), `gtd` fails
|
|
291
|
+
immediately with a clear error instead of looping.
|
|
292
|
+
|
|
293
|
+
## The 17 states
|
|
294
|
+
|
|
295
|
+
Each state has a **condition** (when it wins), a deterministic **action**, the
|
|
296
|
+
**commit(s)** it produces, and where it **advances**. States marked
|
|
297
|
+
**auto-advance** re-run `gtd` themselves; **STOP** states hand control to a
|
|
298
|
+
human; **edge-only** states render no prompt at all — the driver performs their
|
|
299
|
+
action and re-resolves silently.
|
|
300
|
+
|
|
301
|
+
| State | Kind | Wins when | Action & commit | Advances to |
|
|
302
|
+
| ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
303
|
+
| **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 |
|
|
304
|
+
| **Escalate** | STOP | ERRORS.md present | none | held until the human deletes ERRORS.md |
|
|
305
|
+
| **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 |
|
|
306
|
+
| **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 |
|
|
307
|
+
| **Planning** | agent, auto | `.gtd` present **and modified**; HEAD `gtd: grilled` or `gtd: planning` | commit the `.gtd/` changes `gtd: planning` | continue decomposing, else → Building |
|
|
308
|
+
| **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 |
|
|
309
|
+
| **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 |
|
|
310
|
+
| **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 |
|
|
311
|
+
| **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 |
|
|
312
|
+
| **Squashing** | agent, auto | no steering files, HEAD `gtd: done`, `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) |
|
|
313
|
+
| **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 |
|
|
314
|
+
| **Await Review** | edge-only, auto | REVIEW.md present and **uncommitted** (freshly written by Clean) | commit REVIEW.md `gtd: awaiting review` | Done (auto, same run) |
|
|
315
|
+
| **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 |
|
|
316
|
+
| **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 |
|
|
317
|
+
| **Grilled** | agent, auto | TODO.md present, no markers, clean tree | commit pending `gtd: grilled` | decompose into `.gtd/` → Planning |
|
|
318
|
+
| **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 (four 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 |
|
|
319
|
+
| **Idle** | STOP | no steering files, clean tree, and nothing to review (HEAD `gtd: done` with `squash` disabled or after Squashing, or no reviewable diff) | none | — |
|
|
320
|
+
|
|
321
|
+
Every prompt also embeds the current `git diff HEAD` (untracked files included)
|
|
322
|
+
inline, plus the last commit subject and working-tree status, so the agent has
|
|
323
|
+
full context.
|
|
324
|
+
|
|
325
|
+
### Review base — four rules
|
|
326
|
+
|
|
327
|
+
The review base (the commit whose diff to HEAD forms the REVIEW.md) is chosen by
|
|
328
|
+
four rules evaluated in priority order:
|
|
329
|
+
|
|
330
|
+
1. **Within a process, first review** — a `gtd: grilling` commit exists after
|
|
331
|
+
the last `gtd: done` (or task start), but no `gtd: awaiting review` yet →
|
|
332
|
+
base = first `gtd: grilling` of the current task cycle; `refDiff` spans the
|
|
333
|
+
whole task.
|
|
334
|
+
2. **Within a process, incremental** — `gtd: awaiting review` also present in
|
|
335
|
+
the current cycle (takes precedence over rule 1) → base = last
|
|
336
|
+
`gtd: awaiting review`; `refDiff` spans only the post-review changes.
|
|
337
|
+
3. **Outside a process, feature branch** — no `gtd: grilling` after the last
|
|
338
|
+
`gtd: done`, and HEAD is not on the default branch → base = merge-base with
|
|
339
|
+
the default branch; `refDiff` spans the whole branch.
|
|
340
|
+
4. **Outside a process, default branch** — no process active and HEAD is on the
|
|
341
|
+
default branch → skip review; `reviewBase`/`refDiff` unset → Idle.
|
|
342
|
+
|
|
343
|
+
In all cases, if the diff from the chosen base to HEAD is empty,
|
|
344
|
+
`reviewBase`/`refDiff` are left unset and the machine settles in Idle.
|
|
345
|
+
|
|
346
|
+
## The fix loops & counter folds
|
|
347
|
+
|
|
348
|
+
Two derived counters drive the budgeted loops. Both are **folded in the
|
|
349
|
+
machine** from flags on the `COMMIT[]` stream — never recomputed at the edge:
|
|
350
|
+
|
|
351
|
+
- **`testFixCount`** — `gtd: errors` commits (test-fix attempts) since the
|
|
352
|
+
**most recent of** {a package start (`gtd: planning` / `gtd: package done`), a
|
|
353
|
+
`gtd: feedback` (start of a review-fix), or a commit that **removed
|
|
354
|
+
ERRORS.md** (a human resume)}. So each test-fix sub-loop, each review-fix
|
|
355
|
+
round, and every human resume starts a **fresh budget**.
|
|
356
|
+
- **`reviewFixCount`** — `gtd: feedback` commits (review-fix rounds) since the
|
|
357
|
+
most recent package start.
|
|
358
|
+
|
|
359
|
+
### Test-fix loop (`fixAttemptCap`, default 3)
|
|
360
|
+
|
|
361
|
+
When Testing's run is red, it writes the captured output and commits
|
|
362
|
+
`gtd: errors`, incrementing `testFixCount`. If the captured output is empty or
|
|
363
|
+
whitespace-only (e.g. a command that exits non-zero with no output), a sentinel
|
|
364
|
+
string is written instead — so FEEDBACK/ERRORS is never empty. Empty FEEDBACK
|
|
365
|
+
remains reserved exclusively for Agentic Review's deliberate approval signal.
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
Building → Testing(red) → Fixing → Testing(red) → … → Testing(green)
|
|
369
|
+
│ │
|
|
370
|
+
└── below cap: FEEDBACK.md, gtd: errors ───┘
|
|
371
|
+
└── at/over the cap: ERRORS.md, gtd: errors → Escalate
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Below the cap, the failure goes to **FEEDBACK.md** and Fixing applies a fix. At
|
|
375
|
+
or over the cap (`testFixCount >= fixAttemptCap`), it goes to **ERRORS.md**
|
|
376
|
+
instead and the loop **stops** at Escalate. The human investigates, then deletes
|
|
377
|
+
ERRORS.md — which **resets the fix-attempt budget** (the next run re-tests and
|
|
378
|
+
grants a fresh `cap` attempts before escalating again). While ERRORS.md exists,
|
|
379
|
+
every run resolves straight back to Escalate.
|
|
380
|
+
|
|
381
|
+
### Review-fix loop & agentic review (`reviewThreshold`, default 3)
|
|
382
|
+
|
|
383
|
+
After a green test run, **Agentic Review** reviews the package's accumulated
|
|
384
|
+
diff against its task specs and **always writes FEEDBACK.md**:
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
Testing(green) → Agentic Review → empty FEEDBACK → Close package → next package
|
|
388
|
+
│
|
|
389
|
+
└─ findings → Fixing(gtd: feedback) → Testing → Agentic Review → …
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
An **empty FEEDBACK.md is approval** — Close package removes the finished
|
|
393
|
+
package directory and commits `gtd: package done`. Findings route to Fixing
|
|
394
|
+
(committed `gtd: feedback`, incrementing `reviewFixCount`), which loops back
|
|
395
|
+
through the test gate and re-reviews. Once `reviewFixCount >= reviewThreshold`,
|
|
396
|
+
Agentic Review **force-approves** (skips the review, closes the package
|
|
397
|
+
directly) so a package can never review-loop forever. Setting
|
|
398
|
+
**`agenticReview: false`** is a kill-switch: every package force-approves
|
|
399
|
+
immediately and the branch proceeds straight to human review.
|
|
400
|
+
|
|
401
|
+
### Per-package close
|
|
402
|
+
|
|
403
|
+
Close package operates on **one** package at a time: it deletes the first
|
|
404
|
+
(finished) numbered directory under `.gtd/` — plus the now-empty `.gtd/` itself
|
|
405
|
+
if it was the last — and commits `gtd: package done`, which sends Building to
|
|
406
|
+
the next package (or Clean once `.gtd/` is gone). Each package thus runs the
|
|
407
|
+
full
|
|
408
|
+
`Building → Testing → Agentic Review → (Fixing → Testing → Agentic Review)* → Close`
|
|
409
|
+
loop before the next one starts.
|
|
410
|
+
|
|
411
|
+
## A typical feature
|
|
412
|
+
|
|
413
|
+
1. **Capture.** Leave a sketch in `TODO.md` (or just some pending code changes),
|
|
414
|
+
then run `gtd`. **New Feature** commits the raw input `gtd: new task`,
|
|
415
|
+
reverts it back to a clean baseline, and seeds an uncommitted `TODO.md` from
|
|
416
|
+
the diff.
|
|
417
|
+
2. **Grill.** Run `gtd` — the **Grilling** agent (planning model) develops the
|
|
418
|
+
plan, appends open questions each marked with a `<!-- user answers here -->`
|
|
419
|
+
line, and leaves `TODO.md` uncommitted; the edge commits `gtd: grilling`.
|
|
420
|
+
While any marker is present, gtd **STOPs** for you.
|
|
421
|
+
3. **Answer.** Open `TODO.md`, replace each `<!-- user answers here -->` with
|
|
422
|
+
your answer, and run `gtd` again. The agent integrates answers, moves them to
|
|
423
|
+
`## Resolved`, and raises fresh questions — repeat until none remain (it
|
|
424
|
+
writes `no open questions — run gtd to plan` with no markers).
|
|
425
|
+
4. **Converge.** A clean tree with no markers resolves to **Grilled**
|
|
426
|
+
(`gtd: grilled`), then **Planning** decomposes `TODO.md` into ordered `.gtd/`
|
|
427
|
+
work packages (`gtd: planning`).
|
|
428
|
+
5. **Build.** Run `gtd` — **Building** first deletes `TODO.md` (when HEAD is
|
|
429
|
+
`gtd: planning` and it is still present, committed under the same
|
|
430
|
+
`gtd: planning` prefix — fires once). It then names the single next package
|
|
431
|
+
and inlines its task files; the agent spawns one parallel subagent per task
|
|
432
|
+
(execution model + TDD) and leaves the work **uncommitted**. The next run is
|
|
433
|
+
**Testing**: the edge commits `gtd: building`, then runs `testCommand`.
|
|
434
|
+
6. **Review each package.** On green, **Agentic Review** writes FEEDBACK.md.
|
|
435
|
+
Empty → **Close package** (`gtd: package done`) and on to the next package;
|
|
436
|
+
findings → **Fixing** → back through the test gate. A red test run drives the
|
|
437
|
+
test-fix loop until green or Escalate.
|
|
438
|
+
7. **Human review.** When `.gtd/` is gone, **Clean** writes a `REVIEW.md` for
|
|
439
|
+
the diff since the review base (uncommitted); **Await Review** (edge-only)
|
|
440
|
+
commits it `gtd: awaiting review` and auto-advances to Done in the same run.
|
|
441
|
+
8. **Approve or revise.** Re-run `gtd` with **no** changes to approve → **Done**
|
|
442
|
+
(`gtd: done`) → **Squashing** → **Idle**. The Squashing agent authors a
|
|
443
|
+
conventional-commits message from the full process diff and squashes all
|
|
444
|
+
intermediate `gtd: *` commits into one with `git reset --soft <base>` +
|
|
445
|
+
`git commit`, then **gtd STOPs**. Post-squash review does not fire
|
|
446
|
+
automatically — it fires only on the next manual `gtd` run (when the squash
|
|
447
|
+
commit is the boundary HEAD and a reviewable diff exists). Squashing fires
|
|
448
|
+
when the tree has no unrelated code dirty — a lone untracked `SQUASH_MSG.md`
|
|
449
|
+
is tolerated and deleted before the squash commit. If unrelated code is dirty
|
|
450
|
+
at `gtd: done`, gtd routes to **New Feature** instead. Set `squash: false` in
|
|
451
|
+
`.gtdrc` to skip squashing and go straight to Idle. Checking off REVIEW.md
|
|
452
|
+
checkboxes (`- [ ]` → `- [x]`) also counts as approval and routes to **Done**
|
|
453
|
+
— they are navigation aids, not feedback. Only **non-checkbox** edits (code
|
|
454
|
+
changes, inline comments, textual annotations in REVIEW.md) trigger **Accept
|
|
455
|
+
Review**, which seeds a fresh `TODO.md` from your feedback, discards your
|
|
456
|
+
code edits, removes `REVIEW.md`, and re-enters Grilling — the loop starts
|
|
457
|
+
over.
|
|
458
|
+
|
|
459
|
+
## Configuration
|
|
460
|
+
|
|
461
|
+
gtd reads an optional `.gtdrc` config file via
|
|
462
|
+
[cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no config, the
|
|
463
|
+
built-in defaults apply. Supported filenames (searched in this order):
|
|
464
|
+
|
|
465
|
+
- `.gtdrc`
|
|
466
|
+
- `.gtdrc.json`
|
|
467
|
+
- `.gtdrc.yaml`
|
|
468
|
+
- `.gtdrc.yml`
|
|
469
|
+
- `gtd.config.json`
|
|
470
|
+
- `gtd.config.yaml`
|
|
471
|
+
|
|
472
|
+
### Schema
|
|
473
|
+
|
|
474
|
+
- **`testCommand`** (string, default `npm run test`) — the command the edge runs
|
|
475
|
+
in the Testing state to verify a built package.
|
|
476
|
+
- **`fixAttemptCap`** (non-negative integer, default `3`) — the test-fix budget:
|
|
477
|
+
how many `gtd: errors` attempts are allowed per sub-loop before the failure is
|
|
478
|
+
escalated to ERRORS.md (Escalate). `0` disables the cap (escalates immediately
|
|
479
|
+
on the first red run).
|
|
480
|
+
- **`reviewThreshold`** (integer ≥ 1, default `3`) — the review-fix budget: how
|
|
481
|
+
many `gtd: feedback` rounds are allowed per package before Agentic Review
|
|
482
|
+
force-approves.
|
|
483
|
+
- **`agenticReview`** (boolean, default `true`) — kill-switch for the
|
|
484
|
+
per-package Agentic Review gate. Set to `false` to skip agentic review
|
|
485
|
+
entirely; every package force-approves and the branch proceeds directly to
|
|
486
|
+
human review.
|
|
487
|
+
- **`squash`** (boolean, default `true`) — after `gtd: done`, collapse all
|
|
488
|
+
intermediate `gtd: *` commits into a single conventional-commits commit via
|
|
489
|
+
`git reset --soft <base>` + `git commit`. Set `false` to keep the granular
|
|
490
|
+
history.
|
|
491
|
+
- **`models`** — model selection for the subagent-spawning states:
|
|
492
|
+
- `planning` — high-reasoning tier (default `claude-opus-4-8`), used by
|
|
493
|
+
`decompose`, `grilling`, `agentic-review`, and `clean`.
|
|
494
|
+
- `execution` — everyday tier (default `claude-sonnet-4-8`), used by
|
|
495
|
+
`building` and `fixing`.
|
|
496
|
+
- `states.*` — per-state overrides keyed by the six agent states: `decompose`
|
|
497
|
+
(shared by the Grilled and Planning states), `grilling`, `building`,
|
|
498
|
+
`fixing`, `agentic-review`, `clean`. Unknown `states` keys are **rejected**.
|
|
499
|
+
|
|
500
|
+
### Validation and errors
|
|
501
|
+
|
|
502
|
+
If a config file fails to load or is invalid, gtd **exits with code 1** and
|
|
503
|
+
writes a human-readable error to **stderr** (never stdout):
|
|
504
|
+
|
|
505
|
+
- **Parse errors** (malformed YAML/JSON) — message includes the offending
|
|
506
|
+
filename, e.g. `gtd: /path/to/.gtdrc: unexpected token`.
|
|
507
|
+
- **Non-object top-level** — a YAML list or `null` at the root is rejected with
|
|
508
|
+
the filename in the message.
|
|
509
|
+
- **Schema violations** — unknown keys or out-of-range values emit
|
|
510
|
+
`Invalid gtd config: <field>: <reason>`. The message is concise and does not
|
|
511
|
+
dump the full type tree.
|
|
512
|
+
- **Missing test binary** — if `testCommand` names an executable that cannot be
|
|
513
|
+
found (`ENOENT`), gtd exits with code 1 and writes
|
|
514
|
+
`gtd: test command not found: <command>` to **stderr**. No stack trace is
|
|
515
|
+
emitted to stdout. A non-zero test exit is _not_ an error — it drives the
|
|
516
|
+
normal red-path (FEEDBACK → Fixing).
|
|
517
|
+
|
|
518
|
+
### Lookup and precedence
|
|
519
|
+
|
|
520
|
+
gtd walks from the current working directory **up to your home directory** (or
|
|
521
|
+
to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
|
|
522
|
+
finds along the way. All found levels are **deep-merged**, with the **innermost
|
|
523
|
+
(cwd) config winning** on conflicts.
|
|
524
|
+
|
|
525
|
+
This makes the worktree-parent case easy: drop a single `.gtdrc` in a shared
|
|
526
|
+
parent directory and it cascades to **all** checkouts/worktrees beneath it,
|
|
527
|
+
while any individual checkout can still override settings with its own `.gtdrc`.
|
|
528
|
+
|
|
529
|
+
### Example
|
|
530
|
+
|
|
531
|
+
```yaml
|
|
532
|
+
# .gtdrc.yaml
|
|
533
|
+
testCommand: pnpm test
|
|
534
|
+
fixAttemptCap: 3
|
|
535
|
+
reviewThreshold: 3
|
|
536
|
+
agenticReview: true
|
|
537
|
+
squash: true
|
|
538
|
+
models:
|
|
539
|
+
planning: claude-opus-4-8
|
|
540
|
+
execution: claude-sonnet-4-8
|
|
541
|
+
states:
|
|
542
|
+
decompose: claude-opus-4-8
|
|
543
|
+
building: claude-sonnet-4-8
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## Build orchestration
|
|
547
|
+
|
|
548
|
+
When a plan is finalized, gtd enters build mode.
|
|
549
|
+
|
|
550
|
+
### Decompose
|
|
551
|
+
|
|
552
|
+
The Grilled / Planning states spawn a planning-model subagent that breaks
|
|
553
|
+
`TODO.md` into executable work packages under `.gtd/`:
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
.gtd/
|
|
557
|
+
01-auth-module/
|
|
558
|
+
01-define-types.md
|
|
559
|
+
02-implement-login.md
|
|
560
|
+
02-api-endpoints/
|
|
561
|
+
01-create-routes.md
|
|
562
|
+
02-add-middleware.md
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Rules:
|
|
566
|
+
|
|
567
|
+
- **Packages are sequential, in ordinal dependency order** — `01-`, `02-`, …;
|
|
568
|
+
the set is frozen once written. Package 02 cannot start until 01 is complete.
|
|
569
|
+
- **Each package is green on its own** — the test suite runs after every
|
|
570
|
+
package, so none may leave the tree red for a later package to fix.
|
|
571
|
+
- **Tasks within a package are parallel and file-disjoint** — one subagent per
|
|
572
|
+
task, no isolation; tasks that would touch the same file are merged into one.
|
|
573
|
+
- **Vertical slices, not horizontal** — each package is a thin, end-to-end
|
|
574
|
+
slice; prefer many thin packages over a "set up infrastructure" package.
|
|
575
|
+
- **Task files are self-contained** — description, acceptance-criteria
|
|
576
|
+
checkboxes, relevant file paths, constraints, and edge cases.
|
|
577
|
+
|
|
578
|
+
Packages carry only their task `.md` files; the edge commits each built package
|
|
579
|
+
`gtd: building`.
|
|
580
|
+
|
|
581
|
+
### Execute
|
|
582
|
+
|
|
583
|
+
Execution is **one package per cycle**. gtd selects the single next package
|
|
584
|
+
itself, names it in the prompt, and inlines its task files' full contents — the
|
|
585
|
+
prompt is self-contained, so the agent never browses `.gtd/` or picks a package.
|
|
586
|
+
A single cycle:
|
|
587
|
+
|
|
588
|
+
1. Spawn parallel execution-model workers for all tasks in the selected package
|
|
589
|
+
(with the `tdd` skill).
|
|
590
|
+
2. If a worker fails (crash/timeout, not a test failure): ask the user to
|
|
591
|
+
retry/skip/abort.
|
|
592
|
+
3. Leave all changes **uncommitted**. Do not commit, do not delete the package
|
|
593
|
+
directory, do not run tests here.
|
|
594
|
+
4. Re-run gtd — the next cycle's edge (Testing) commits the work `gtd: building`
|
|
595
|
+
and runs `testCommand` to verify it.
|
|
596
|
+
|
|
597
|
+
Verification is deterministic and lives in the edge, not the prompt: gtd runs
|
|
598
|
+
the configured `testCommand` itself, captures stdout + stderr + the exit code,
|
|
599
|
+
and the **machine** branches on it (green → Agentic Review; red below cap →
|
|
600
|
+
Fixing; red at/over cap → Escalate).
|
|
601
|
+
|
|
602
|
+
## Q&A format inside TODO.md
|
|
603
|
+
|
|
604
|
+
The agent never asks the user clarifying questions directly — it records
|
|
605
|
+
uncertainty in `TODO.md` under `## Open Questions` instead. The grilling phase
|
|
606
|
+
is gated by a single **convergence marker**: every open question carries a
|
|
607
|
+
`<!-- user answers here -->` line directly beneath it. While _any_ marker is
|
|
608
|
+
present, gtd **STOPs** and waits for you to answer inline.
|
|
609
|
+
|
|
610
|
+
The `## Open Questions` section lives at the TOP of TODO.md (before the plan
|
|
611
|
+
body). Each question looks like this:
|
|
612
|
+
|
|
613
|
+
```markdown
|
|
614
|
+
### What should pagination default to?
|
|
615
|
+
|
|
616
|
+
**Recommendation:** 25 per page — matches the admin tables elsewhere.
|
|
617
|
+
|
|
618
|
+
<!-- user answers here -->
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
To answer, replace the comment with your response:
|
|
622
|
+
|
|
623
|
+
```markdown
|
|
624
|
+
### What should pagination default to?
|
|
625
|
+
|
|
626
|
+
**Recommendation:** 25 per page — matches the admin tables elsewhere.
|
|
627
|
+
|
|
628
|
+
50 — these tables get long and 25 wastes a click for most users.
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
On the next run the agent integrates the answer into the plan body and moves the
|
|
632
|
+
question to the `## Resolved` graveyard at the bottom:
|
|
633
|
+
|
|
634
|
+
```markdown
|
|
635
|
+
## Resolved
|
|
636
|
+
|
|
637
|
+
### What should pagination default to?
|
|
638
|
+
|
|
639
|
+
**Recommendation:** 25 per page — matches the admin tables elsewhere.
|
|
640
|
+
|
|
641
|
+
**Answer:** 50 — these tables get long and 25 wastes a click for most users.
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
When there are genuinely no open questions left, the agent writes the sentinel
|
|
645
|
+
line `no open questions — run gtd to plan` and leaves **no** markers — a clean
|
|
646
|
+
tree with no markers is what advances the plan to **Grilled** and decomposition.
|
|
647
|
+
|
|
648
|
+
## Formatting
|
|
649
|
+
|
|
650
|
+
gtd ships a `format` subcommand — the **only** subcommand — that formats a
|
|
651
|
+
markdown file in place:
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
gtd format <file>
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
It uses a bundled prettier with a fixed, gtd-owned config (`parser: "markdown"`,
|
|
658
|
+
`printWidth: 80`, `proseWrap: "always"`). The host repo's `.prettierrc` is
|
|
659
|
+
**intentionally ignored** — determinism across consumer repos matters more than
|
|
660
|
+
local style preferences.
|
|
661
|
+
|
|
662
|
+
The grilling and clean prompts instruct the agent to run this command after
|
|
663
|
+
every edit to `TODO.md` or `REVIEW.md`, so those files stay consistently
|
|
664
|
+
formatted regardless of the host project's toolchain.
|
|
665
|
+
|
|
666
|
+
### Error handling
|
|
667
|
+
|
|
668
|
+
All errors exit with **code 1** and write a message to **stderr**:
|
|
669
|
+
|
|
670
|
+
- **Missing path** — `gtd format` with no argument:
|
|
671
|
+
`gtd format: missing file path argument`
|
|
672
|
+
- **Extra arguments** — `gtd format a.md b.md`:
|
|
673
|
+
`gtd format: too many arguments — expected one path, got: …`
|
|
674
|
+
- **Non-markdown file** — any extension other than `.md` or `.markdown`
|
|
675
|
+
(case-insensitive):
|
|
676
|
+
`gtd format: <file> is not a markdown file (expected .md or .markdown)`
|
|
677
|
+
- **File not found** — the path does not exist:
|
|
678
|
+
`gtd: skipped formatting <file>: not found`
|
|
679
|
+
|
|
680
|
+
> [!NOTE] Upgrading gtd may reflow existing `TODO.md` files if the bundled
|
|
681
|
+
> prettier major version changes.
|
|
682
|
+
|
|
683
|
+
## Development
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
npm install
|
|
687
|
+
npm run dev # run from source, no build (node dev/run.mjs)
|
|
688
|
+
npm run build # tsup → dist/gtd.bundle.mjs
|
|
689
|
+
npm test # vitest unit tests (the pure resolver) — --project unit
|
|
690
|
+
npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
|
|
691
|
+
npm run test:mutation # StrykerJS mutation testing
|
|
692
|
+
npm run typecheck
|
|
693
|
+
npm run lint
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Pre-commit hook
|
|
697
|
+
|
|
698
|
+
A pre-commit hook is installed automatically via the `prepare` script when you
|
|
699
|
+
run `npm install` on a fresh clone — no manual setup needed.
|
|
700
|
+
|
|
701
|
+
The hook runs [lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
702
|
+
[Prettier](https://prettier.io/), formatting every staged file before each
|
|
703
|
+
commit:
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
prettier --ignore-unknown --write
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
This mirrors the `format:check` step enforced in CI (`prettier --check .`),
|
|
710
|
+
keeping committed code consistently formatted without requiring a separate
|
|
711
|
+
manual format pass.
|
|
712
|
+
|
|
713
|
+
`npm run dev` runs `src/main.ts` directly via Node's native TypeScript
|
|
714
|
+
type-stripping (requires Node 22.6+). It registers `dev/hooks.mjs`, which fills
|
|
715
|
+
the two gaps the tsup build otherwise covers: resolving `./Foo.js` specifiers to
|
|
716
|
+
the on-disk `./Foo.ts`, and importing `*.md` prompt files as text. Pass CLI args
|
|
717
|
+
after `--`, e.g. `npm run dev -- format <file>`. The helpers live in `dev/`
|
|
718
|
+
rather than `scripts/` because tsup wipes `dist/` (`clean: true`) on build.
|
|
719
|
+
|
|
720
|
+
The decision core (`src/Machine.ts`) is pure and IO-free, so the whole 17-state
|
|
721
|
+
ladder and both counter folds are trivially unit-testable in isolation; all
|
|
722
|
+
git/filesystem IO is confined to the edge (`src/Events.ts`).
|
|
723
|
+
|
|
724
|
+
`npm run build` produces `dist/gtd.bundle.mjs`, which npm exposes as the `gtd`
|
|
725
|
+
binary via the `bin` field in `package.json`.
|
|
726
|
+
|
|
727
|
+
### Mutation testing
|
|
728
|
+
|
|
729
|
+
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min).
|
|
730
|
+
The single `stryker.config.json` mutates six core files:
|
|
731
|
+
|
|
732
|
+
```
|
|
733
|
+
src/Machine.ts src/Prompt.ts src/Config.ts
|
|
734
|
+
src/Format.ts src/State.ts src/Events.ts
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
`src/Git.ts` is excluded: the Cucumber harness stubs git at the Effect boundary,
|
|
738
|
+
so Git.ts mutants have zero in-memory coverage. Measuring its post-refactor
|
|
739
|
+
Live-tier kill rate is a follow-up before re-including it.
|
|
740
|
+
|
|
741
|
+
**`process.chdir()` gotcha (resolved).** `@stryker-mutator/vitest-runner`
|
|
742
|
+
hardcodes `pool: 'threads'` internally, and `process.chdir()` is unsupported in
|
|
743
|
+
worker threads. Before the cwd refactor (package 01), four test files
|
|
744
|
+
(`Events.test.ts`, `Git.test.ts`, `Config.test.ts`, `TestRunner.test.ts`) had to
|
|
745
|
+
be excluded from all Stryker runs. The refactor eliminated those calls, letting
|
|
746
|
+
all four files rejoin the run.
|
|
747
|
+
|
|
748
|
+
Two additional notes: `vitest.related` is disabled for feature-file runs because
|
|
749
|
+
feature files don't import source files directly (Stryker's coverage-based
|
|
750
|
+
filtering would assign zero tests to every mutant). Compile-error mutants are
|
|
751
|
+
counted as kills by the TypeScript checker — they represent real signal, not a
|
|
752
|
+
configuration problem.
|
|
753
|
+
|
|
754
|
+
Run `npm run test:mutation` after making changes to the mutated files to check
|
|
755
|
+
whether surviving mutants increased. The HTML report lands in
|
|
756
|
+
`reports/mutation/mutation.html` (git-ignored).
|
|
757
|
+
|
|
758
|
+
### Mutation testing
|
|
759
|
+
|
|
760
|
+
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min).
|
|
761
|
+
The single `stryker.config.json` mutates six core files:
|
|
762
|
+
|
|
763
|
+
```
|
|
764
|
+
src/Machine.ts src/Prompt.ts src/Config.ts
|
|
765
|
+
src/Format.ts src/State.ts src/Events.ts
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`src/Git.ts` is excluded: the Cucumber harness stubs git at the Effect boundary,
|
|
769
|
+
so Git.ts mutants have zero in-memory coverage. Measuring its post-refactor
|
|
770
|
+
Live-tier kill rate is a follow-up before re-including it.
|
|
771
|
+
|
|
772
|
+
**`process.chdir()` gotcha (resolved).** `@stryker-mutator/vitest-runner`
|
|
773
|
+
hardcodes `pool: 'threads'` internally, and `process.chdir()` is unsupported in
|
|
774
|
+
worker threads. Before the cwd refactor (package 01), four test files
|
|
775
|
+
(`Events.test.ts`, `Git.test.ts`, `Config.test.ts`, `TestRunner.test.ts`) had to
|
|
776
|
+
be excluded from all Stryker runs. The refactor eliminated those calls, letting
|
|
777
|
+
all four files rejoin the run.
|
|
778
|
+
|
|
779
|
+
Two additional notes: `vitest.related` is disabled for feature-file runs because
|
|
780
|
+
feature files don't import source files directly (Stryker's coverage-based
|
|
781
|
+
filtering would assign zero tests to every mutant). Compile-error mutants are
|
|
782
|
+
counted as kills by the TypeScript checker — they represent real signal, not a
|
|
783
|
+
configuration problem.
|
|
784
|
+
|
|
785
|
+
Run `npm run test:mutation` after making changes to the mutated files to check
|
|
786
|
+
whether surviving mutants increased. The HTML report lands in
|
|
787
|
+
`reports/mutation/mutation.html` (git-ignored).
|
|
788
|
+
|
|
789
|
+
## Releasing
|
|
790
|
+
|
|
791
|
+
Releases are automatic. Push releasable Conventional Commits (`fix:`, `feat:`,
|
|
792
|
+
or breaking changes) to `main` and the Release workflow runs the tests, then
|
|
793
|
+
`npx semantic-release`. Semantic-release computes the next version, writes it
|
|
794
|
+
into `package.json`, builds the bundle, commits the bump back as
|
|
795
|
+
`chore(release): X.Y.Z [skip ci]`, tags `vX.Y.Z`, and creates the GitHub release
|
|
796
|
+
with `gtd.bundle.mjs` attached.
|
|
797
|
+
|
|
798
|
+
## License
|
|
799
|
+
|
|
800
|
+
MIT
|