@pmelab/gtd 2.6.0 → 2.8.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/docs/cli.md ADDED
@@ -0,0 +1,356 @@
1
+ # CLI reference
2
+
3
+ ```
4
+ Usage: gtd [command] [options]
5
+
6
+ Commands:
7
+ step Advance the workflow as the human actor (to fixpoint)
8
+ step-agent Advance the workflow as the agent actor (to fixpoint)
9
+ next Print the prompt for whichever actor is awaited (no mutation)
10
+ status Predict the next commit and state from the working tree (no mutation)
11
+ review <target> Anchor an ad-hoc human review against a git ref or branch
12
+ questions List open questions from the active grilling/architecting doc
13
+ changesets List changesets/files from the active review doc
14
+ format <file> Format a markdown file in place
15
+
16
+ Options:
17
+ --json Output structured JSON instead of plain text
18
+ --version, -v Print version and exit
19
+ --help, -h Print this help and exit
20
+ ```
21
+
22
+ `--version` (`-v`) and `--help` (`-h`) short-circuit before any git or
23
+ repository-state work — they run outside a repo and in any repo state. Bare
24
+ `gtd` (no subcommand) is a usage error: it prints the help text and exits 1
25
+ without touching the repository. Every other command must be run from the
26
+ **repository root** — gtd derives steering files, diffs, and pathspecs relative
27
+ to cwd, so it refuses with a clear error if invoked from a subdirectory.
28
+
29
+ `--json` is the only long option. Any other `--` option (including a typo like
30
+ `--jsn`) is rejected with a usage error rather than silently ignored, so a
31
+ mistyped flag can never degrade a JSON caller to plain-text mode.
32
+
33
+ One nuance to "(no mutation)": `next` and `status` never author commits or
34
+ change workflow state, but while a human review is pending they do maintain the
35
+ review checkout window (closing it to read state, re-arming it on the way out —
36
+ see [Human review gate](workflow.md#human-review-gate)), which transiently moves
37
+ HEAD and the index. The working tree is never touched.
38
+
39
+ ## `gtd step` / `gtd step-agent`
40
+
41
+ Both drive the **same fixpoint loop** — gather → resolve → perform the returned
42
+ edge action → repeat — differing only in which actor's turn they are allowed to
43
+ capture:
44
+
45
+ - **`gtd step`** captures the **human** turn at whichever gate is awaiting one.
46
+ - **`gtd step-agent`** captures the **agent** turn.
47
+
48
+ **Fixpoint advance.** A single invocation may author several commits: it authors
49
+ the awaited actor's turn commit, then keeps performing any further mid-chain
50
+ routing (a test run, a routing commit, a package close, …) until it reaches a
51
+ rest where a prompt would be shown, or a fixpoint where nothing changed.
52
+ `gtd step`/`gtd step-agent` never print a prompt themselves — that's
53
+ `gtd next`'s job.
54
+
55
+ **Idempotence.** Re-running the same command again once the tree is settled at a
56
+ rest authors **zero** new commits. It exits 0 while the rest still awaits that
57
+ command's actor (an inert empty agent turn, the idle health check); once the
58
+ rest awaits the _other_ actor, the re-run is an out-of-turn refusal — still zero
59
+ commits, but non-zero exit.
60
+
61
+ **Out-of-turn refusal.** Human and agent turns are strictly separated: the wrong
62
+ mutator always errors, at every state, on clean and dirty trees alike.
63
+ `gtd step-agent` while a human turn is awaited refuses with
64
+ `"<state> awaits a human turn — run \`gtd step\`"`; `gtd
65
+ step`while an agent turn is awaited refuses with`"<state> awaits an agent turn —
66
+ run \`gtd
67
+ 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:
68
+ planning` commit lands) stay pending in the working tree and ride along as input
69
+ to the agent's next captured turn; left unamended, the build proceeds.
70
+
71
+ **Red-test fixpoints exit 0.** A red test run below the fix-attempt cap (or the
72
+ health-fix cap) still writes its findings and commits — it is a normal,
73
+ successful step of the loop, not a failure of the `step`/`step-agent`
74
+ invocation. `step`/`step-agent` only exit non-zero for a genuine refusal or an
75
+ operational error (bad config, missing test binary, corrupted state).
76
+
77
+ **Output.** Plain mode prints one `committed: <subject>` line per commit this
78
+ invocation authored (oldest→newest), then a final `state: <state>` line:
79
+
80
+ ```
81
+ committed: gtd(human): grilling
82
+ committed: gtd: architecting
83
+ state: architecting
84
+ ```
85
+
86
+ `--json` emits `{state, actions, commits}` instead (see
87
+ [JSON schemas](#json-schemas)).
88
+
89
+ ## `gtd next`
90
+
91
+ Pure prompt emitter — it **never mutates** the repository. It reports whichever
92
+ actor is currently awaited and, if the tree is at a genuine rest, the full
93
+ prompt for that actor.
94
+
95
+ **Purity.** No commits, no file writes, no test runs — `gtd next` only gathers
96
+ and resolves.
97
+
98
+ **Dirty-tree refusal.** If the working tree has pending changes outside the
99
+ steering-file set, `gtd next` refuses rather than guess at a prompt for a state
100
+ that hasn't been captured yet:
101
+
102
+ ```
103
+ 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)
104
+ ```
105
+
106
+ **Pending.** If HEAD is mid-chain — bookkeeping the next `step`/`step-agent`
107
+ invocation would perform before reaching a rest — `gtd next` reports
108
+ `pending: true` with no prompt. Mid-chain bookkeeping is invoker-agnostic, so
109
+ either mutator resumes it; the report names the actor whose chain it is. In
110
+ plain mode an agent-driven checkpoint prints `"mid-chain checkpoint — run \`gtd
111
+ step-agent\` to continue, then run \`gtd next\`
112
+ again"`, a human-driven one prints `"mid-chain checkpoint — run \`gtd step\` to
113
+ continue"`.
114
+
115
+ **Agent tail lines.** In plain-mode output, a prompt for the **agent** actor
116
+ ends with the pinned tail:
117
+
118
+ ```
119
+ Finish your turn by running `gtd step-agent`. Then run `gtd next` and follow
120
+ its output — repeat this cycle as long as the output is addressed to you (the
121
+ agent); when it awaits the human, stop and hand off.
122
+ ```
123
+
124
+ The first sentence closes the current turn; the second closes the outer loop —
125
+ it is what lets a plain-text agent chain multiple iterations (e.g. successive
126
+ test/fix cycles) without an external driver, until a human gate is reached.
127
+ Human-actor prompts carry no tail. `--json` output never embeds the tail into
128
+ `prompt` either — the structured `actor` field (see
129
+ [JSON schemas](#json-schemas) below) carries the same information: `"agent"`
130
+ means another agent round, `"human"` means stop and hand off.
131
+
132
+ ## `gtd status`
133
+
134
+ Pure, read-only **dry-run prediction** — the same gather+resolve `gtd next`
135
+ runs, but reporting a prediction of the next turn rather than the actual prompt.
136
+ Performs no git mutation, no test run, no file write — guaranteed side-effect
137
+ free, including on a dirty tree.
138
+
139
+ Prints four fields:
140
+
141
+ ```
142
+ State: grilling
143
+ Awaits: human
144
+ Predicted commit: gtd(human): grilling
145
+ Predicted state: grilling
146
+ ```
147
+
148
+ - **State** — the currently resolved state.
149
+ - **Awaits** — the actor (`human` or `agent`) whose turn it is.
150
+ - **Predicted commit** — the subject `step`/`step-agent` would author next, or
151
+ `(none)` at a fixpoint (e.g. idle with nothing to do).
152
+ - **Predicted state** — the state that commit would land in.
153
+
154
+ `gtd status` takes no arguments — extra positional args are rejected.
155
+
156
+ ## `gtd review <target>`
157
+
158
+ A pure mutator that **anchors, then exits** — it never prints a prompt itself.
159
+ Use it to start an ad-hoc human review against an explicit git ref or branch,
160
+ independent of the automatic review base the workflow otherwise computes.
161
+
162
+ 1. Refuses on a dirty tree.
163
+ 2. Resolves `<target>` via merge-base semantics and computes the diff HEAD adds
164
+ over `merge-base(<target>, HEAD)`.
165
+ 3. Refuses if that diff is empty after filtering ("nothing to review").
166
+ 4. Authors exactly one commit: `gtd: reviewing <full-hash-of-the-base>`.
167
+ 5. Prints a short confirmation pointing at `gtd next` — it does **not** print
168
+ the review prompt itself.
169
+
170
+ ```bash
171
+ gtd review main
172
+ # anchored review at <hash> — run `gtd next` to get the review prompt
173
+ gtd next --json
174
+ # {"actor":"agent", ...} — the review-record prompt scoped to that anchor
175
+ ```
176
+
177
+ Errors (all exit 1, message on stderr):
178
+
179
+ - Missing target: `gtd review: missing target argument`
180
+ - Extra arguments:
181
+ `gtd review: too many arguments — expected one target, got: …`
182
+ - Unresolvable ref: `gtd review: cannot resolve ref '<target>': <error message>`
183
+ - Empty diff:
184
+ `gtd review: nothing to review (<target> diff is empty after filtering)`
185
+
186
+ ## `gtd questions` / `gtd changesets`
187
+
188
+ Pure, read-only reporters — no dirty-tree check, no mutation — that parse the
189
+ structured content out of the active grilling/architecting and review documents,
190
+ for a future UI:
191
+
192
+ - **`gtd questions`** reads whichever of `.gtd/TODO.md` / `.gtd/ARCHITECTURE.md`
193
+ is present and reports its `## Open Questions` list (see
194
+ [Structured grilling/architecting and review files](workflow.md#structured-grillingarchitecting-and-review-files)).
195
+ Reports an empty list when neither file exists.
196
+ - **`gtd changesets`** reads `.gtd/REVIEW.md`, if present, and reports its
197
+ chunks/file-pointer list. Reports an empty list when the file doesn't exist.
198
+
199
+ Both take no arguments and always exit 0 — a malformed file is reported via the
200
+ `errors` field/lines rather than failing the command (the same diagnosis
201
+ `gtd step-agent` would refuse the agent's next turn capture with).
202
+
203
+ ```bash
204
+ gtd questions --json
205
+ # {"file":".gtd/TODO.md","questions":[{"question":"Which operations?","status":"suggested","text":"add and subtract."}],"errors":[]}
206
+ gtd changesets --json
207
+ # {"file":".gtd/REVIEW.md","shortHash":"abc1234","fullHash":"abc1234...","changesets":[...],"errors":[]}
208
+ ```
209
+
210
+ ## `gtd format <file>`
211
+
212
+ Formats a markdown file in place with a bundled prettier (`parser: "markdown"`,
213
+ `printWidth: 80`, `proseWrap: "always"`), ignoring the host repo's own
214
+ `.prettierrc` so `.gtd/TODO.md`/`.gtd/REVIEW.md` stay consistently formatted
215
+ regardless of the host project's toolchain. Rejects `--json` (exit 1,
216
+ `gtd format does not accept --json`) — it is a plain file operation, not a v2
217
+ state command.
218
+
219
+ Errors (all exit 1, message on stderr):
220
+
221
+ - Missing path: `gtd format: missing file path argument`
222
+ - Extra arguments: `gtd format: too many arguments — expected one path, got: …`
223
+ - Non-markdown file:
224
+ `gtd format: <file> is not a markdown file (expected .md or .markdown)`
225
+ - File not found: `gtd: skipped formatting <file>: not found`
226
+
227
+ ## JSON schemas
228
+
229
+ Pass `--json` to `step`, `step-agent`, `next`, `status`, `review`, `questions`,
230
+ or `changesets` for machine-readable single-line JSON output instead of plain
231
+ text.
232
+
233
+ **`step` / `step-agent`** — `{state, actions, commits}`:
234
+
235
+ ```json
236
+ {
237
+ "state": "architecting",
238
+ "actions": ["capture the human turn as \"gtd(human): grilling\""],
239
+ "commits": ["gtd(human): grilling", "gtd: architecting"]
240
+ }
241
+ ```
242
+
243
+ - `state` — the final resolved state after the fixpoint loop settled.
244
+ - `actions` — human-readable descriptions of every edge action this invocation
245
+ performed, oldest→newest.
246
+ - `commits` — every commit subject this invocation authored, oldest→newest.
247
+
248
+ **`next`** — `{state, actor, pending, prompt}`:
249
+
250
+ ```json
251
+ {
252
+ "state": "building",
253
+ "actor": "agent",
254
+ "pending": false,
255
+ "prompt": "..."
256
+ }
257
+ ```
258
+
259
+ - `state` — the resolved state.
260
+ - `actor` — `"human"` or `"agent"`: who owns the next move. This is the single
261
+ loop-driver signal: `"agent"` means proceed with another round — act on
262
+ `prompt` when present, then run `gtd step-agent`; at an agent-driven pending
263
+ checkpoint (`prompt` is `null`, nothing to act on) just run `gtd step-agent`.
264
+ `"human"` means halt and hand off (a human rest, whose prompt body already
265
+ tells the human what to do, or a human-driven pending checkpoint resumed by
266
+ `gtd step`).
267
+ - `pending` — `true` at a mid-chain HEAD (no prompt yet — resume with a mutator
268
+ first); `false` at a genuine rest.
269
+ - `prompt` — the full prompt markdown when `pending` is `false`, else `null`.
270
+
271
+ **`status`** — `{state, actor, predictedCommit, predictedState}`:
272
+
273
+ ```json
274
+ {
275
+ "state": "grilling",
276
+ "actor": "human",
277
+ "predictedCommit": "gtd(human): grilling",
278
+ "predictedState": "grilling"
279
+ }
280
+ ```
281
+
282
+ `predictedCommit` is `null` when the next invocation would author nothing (e.g.
283
+ idle with a green health check).
284
+
285
+ **`questions`** — `{file, questions, errors}`:
286
+
287
+ ```json
288
+ {
289
+ "file": ".gtd/TODO.md",
290
+ "questions": [
291
+ {
292
+ "question": "Which operations?",
293
+ "status": "suggested",
294
+ "text": "add and subtract."
295
+ }
296
+ ],
297
+ "errors": []
298
+ }
299
+ ```
300
+
301
+ `file` is `null` when neither `.gtd/TODO.md` nor `.gtd/ARCHITECTURE.md` is
302
+ present. `errors` lists any structural problems in the file — the same diagnosis
303
+ that would make `gtd step-agent` refuse the agent's next turn capture.
304
+
305
+ **`changesets`** — `{file, shortHash, fullHash, changesets, errors}`:
306
+
307
+ ```json
308
+ {
309
+ "file": ".gtd/REVIEW.md",
310
+ "shortHash": "abc1234",
311
+ "fullHash": "abc1234def5678901234567890123456789abcd",
312
+ "changesets": [
313
+ {
314
+ "title": "Add calculator",
315
+ "description": "New add function for the calculator.",
316
+ "files": [{ "path": "./src/calc.ts", "line": 1, "checked": false }]
317
+ }
318
+ ],
319
+ "errors": []
320
+ }
321
+ ```
322
+
323
+ `file` is `null` when `.gtd/REVIEW.md` doesn't exist. `errors` mirrors
324
+ `questions`' field above.
325
+
326
+ **Error envelope** — every command, in `--json` mode, reports failures inside
327
+ the JSON object rather than as unstructured text, and still exits 1:
328
+
329
+ ```json
330
+ { "state": "error", "prompt": "<message>" }
331
+ ```
332
+
333
+ There is no auto-advance flag anywhere in the wire format — `actor` replaces it.
334
+ The caller decides whether to keep looping based on `actor` (halt on `"human"`)
335
+ and `pending` (re-run `step`/`step-agent` first when `true`), not on a boolean
336
+ auto-advance flag.
337
+
338
+ ## Repository requirements
339
+
340
+ - **Single writer, linear branch.** State is folded from **first-parent**
341
+ history only. A merge commit at HEAD is unsupported (documented, not handled)
342
+ — it degrades gracefully on the default branch rather than crashing, but do
343
+ not rely on merge commits mid-cycle.
344
+ - **Test/build artifacts must be gitignored.** This is **load-bearing**, not a
345
+ style preference: every fixpoint hop in `gtd step`/`gtd step-agent` detects
346
+ "clean" via `git status --porcelain`, which silently omits anything matched by
347
+ `.gitignore`. If your `testCommand` (or the build it triggers) writes
348
+ tracked-but-untracked output — a `dist/`, a coverage report, a log file — into
349
+ the working tree, the tree never goes clean after a green test run, and the
350
+ fixpoint loop cannot converge: it will either loop forever re-detecting a
351
+ "dirty" boundary or misclassify build output as the human's next feature
352
+ capture. Gitignore every path your test/build toolchain writes before wiring
353
+ gtd into a repo.
354
+ - **Repository root invocation.** Every subcommand except `--help`/`--version`
355
+ must run from the git repository root — steering files and diffs are resolved
356
+ against the process cwd.
@@ -0,0 +1,113 @@
1
+ # Configuration
2
+
3
+ gtd reads an optional `.gtdrc` config file via
4
+ [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no config, the
5
+ built-in defaults apply. Supported filenames (searched in this order):
6
+
7
+ - `.gtdrc`
8
+ - `.gtdrc.json`
9
+ - `.gtdrc.yaml`
10
+ - `.gtdrc.yml`
11
+ - `gtd.config.json`
12
+ - `gtd.config.yaml`
13
+
14
+ ## Schema
15
+
16
+ - **`testCommand`** (string, default `npm run test`) — the command the edge runs
17
+ after a build turn, and on the idle health-check path.
18
+ - **`fixAttemptCap`** (non-negative integer, default `3`) — the test-fix budget:
19
+ how many `gtd: errors` attempts are allowed per sub-loop before the failure is
20
+ escalated to `.gtd/ERRORS.md` (Escalate). `0` disables the cap (escalates
21
+ immediately on the first red run). Also reused as the health-fix budget — no
22
+ separate config key.
23
+ - **`reviewThreshold`** (integer ≥ 1, default `3`) — the review-fix budget: how
24
+ many agentic-review findings rounds are allowed per package before Agentic
25
+ Review force-approves.
26
+ - **`agenticReview`** (boolean, default `true`) — kill-switch for the
27
+ per-package Agentic Review gate. Set `false` to force-approve every package
28
+ and proceed directly to human review.
29
+ - **`squash`** (boolean, default `true`) — after `gtd: done` (or, once learning
30
+ has run, `gtd: learning applied`), collapse the cycle's `gtd: *` commits into
31
+ a single conventional-commits commit. Set `false` to keep the granular
32
+ history.
33
+ - **`learning`** (boolean, default `true`) — after `gtd: done` (or the
34
+ health-fix path's green re-test), distill durable lessons from the cycle into
35
+ `.gtd/LEARNINGS.md`, have a human review them, then integrate them into the
36
+ project's own docs before the squash decision runs. Set `false` to skip the
37
+ phase entirely — independent of `squash`.
38
+ - **`decisionLog`** (boolean, default `true`) — squashing records this cycle's
39
+ resolved open questions as a `## Decisions` section in the squash commit
40
+ message; grilling/architecting inline every past squash commit's section,
41
+ oldest to newest, as prior-decision context. Set `false` to skip both.
42
+ - **`models`** — model selection for the subagent-spawning states:
43
+ - `planning` — high-reasoning tier (default `claude-opus-4-8`), used by
44
+ `decompose` (the `grilled`/`planning` states), `grilling`, `architecting`,
45
+ `agentic-review`, and `clean` (the `review`/`squashing`/`learning`/
46
+ `learning-apply` states).
47
+ - `execution` — everyday tier (default `claude-sonnet-4-8`), used by
48
+ `building` and `fixing`.
49
+ - `states.*` — per-state overrides keyed by `decompose`, `grilling`,
50
+ `architecting`, `building`, `fixing`, `agentic-review`, `clean`. Unknown
51
+ `states` keys are **rejected**.
52
+ - **`$schema`** (string, optional) — stripped before validation, so it never
53
+ counts as an unknown key. Point it at the published schema for editor-backed
54
+ autocompletion. A `schema.json` is generated from the config schema at build
55
+ time and ships with the package.
56
+
57
+ ## Validation and errors
58
+
59
+ If a config file fails to load or is invalid, gtd **exits with code 1** and
60
+ writes a human-readable error to **stderr** (never stdout):
61
+
62
+ - **Parse errors** (malformed YAML/JSON) — message includes the offending
63
+ filename.
64
+ - **Non-object top-level** — a YAML list or `null` at the root is rejected with
65
+ the filename in the message.
66
+ - **Schema violations** — unknown keys or out-of-range values emit
67
+ `Invalid gtd config: <field>: <reason>`.
68
+ - **Missing test binary** — if `testCommand` names an executable that cannot be
69
+ found (`ENOENT`), gtd exits 1 with `gtd: test command not found: <command>` on
70
+ stderr. A non-zero test _exit code_ is not an error — it drives the normal red
71
+ path.
72
+
73
+ ## Lookup and precedence
74
+
75
+ gtd walks from the current working directory **up to your home directory** (or
76
+ to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
77
+ finds along the way. All found levels are **deep-merged**, with the **innermost
78
+ (cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
79
+ directory cascades to every checkout beneath it, while any individual checkout
80
+ can still override settings with its own `.gtdrc`.
81
+
82
+ ## Auto-init
83
+
84
+ On every **state command** (`step`, `step-agent`, `next`, `status`, `review`)
85
+ that has passed the repo-root guard, if the cwd→root walk finds **no** config
86
+ anywhere, gtd creates and commits a starter `.gtdrc.json` at the repository root
87
+ containing only a `$schema` link. Auto-init never runs for `--version`/`--help`,
88
+ `format`, bare/unknown commands, or an invocation refused by the repo-root guard
89
+ — those perform no repository mutation of any kind. On a repo with no commits
90
+ yet, or whose HEAD is a plain (non-`gtd:`) commit, the stub is committed as its
91
+ own `chore: add .gtdrc.json`. If HEAD is already a `gtd:`-owned commit
92
+ (mid-workflow), the stub is instead **amended into HEAD** — stacking a fresh
93
+ boundary commit there would produce an unrecognized HEAD most workflow states
94
+ can't resolve past.
95
+
96
+ ## Example
97
+
98
+ ```yaml
99
+ # .gtdrc.yaml
100
+ testCommand: pnpm test
101
+ fixAttemptCap: 3
102
+ reviewThreshold: 3
103
+ agenticReview: true
104
+ squash: true
105
+ learning: true
106
+ decisionLog: true
107
+ models:
108
+ planning: claude-opus-4-8
109
+ execution: claude-sonnet-4-8
110
+ states:
111
+ decompose: claude-opus-4-8
112
+ building: claude-sonnet-4-8
113
+ ```
@@ -0,0 +1,82 @@
1
+ # Development
2
+
3
+ ```bash
4
+ npm install
5
+ npm run dev # run from source, no build (node dev/run.mjs)
6
+ npm run build # tsdown → dist/gtd.bundle.mjs
7
+ npm test # format:check, typecheck, lint, unit + e2e tests, fallow
8
+ npm run test:unit # vitest unit tests (the pure resolver) — --project unit
9
+ npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
10
+ npm run test:mutation # StrykerJS mutation testing (manual only, ~2 min)
11
+ npm run typecheck
12
+ npm run lint
13
+ ```
14
+
15
+ ## Pre-commit hook
16
+
17
+ A pre-commit hook is installed automatically via the `prepare` script when you
18
+ run `npm install` on a fresh clone — no manual setup needed. The hook runs
19
+ [lint-staged](https://github.com/lint-staged/lint-staged) with
20
+ [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), formatting every staged
21
+ file before each commit (`oxfmt --no-error-on-unmatched-pattern --write`),
22
+ mirroring the `format:check` step enforced in CI (`oxfmt --check .`).
23
+
24
+ ## Prompt templates
25
+
26
+ Each prompt-bearing state has a self-contained Eta template in
27
+ `src/prompts/*.md` that owns its full prompt — header, context, and body. Shared
28
+ fragments live as partials in `src/prompts/partials/`: `header`, the context
29
+ renderers (`diff`, `feedback`, `package`), and the single `agent-turn` tail
30
+ partial (the pinned "Finish your turn by running `gtd step-agent`. Then run
31
+ `gtd next` …" loop-closing instructions).
32
+
33
+ At module load, `src/Prompt.ts` registers every template on a single `new Eta()`
34
+ instance via `loadTemplate`. `readFile` and `resolvePath` are nulled afterward
35
+ so rendering resolves exclusively from the in-memory cache — the compiled ESM
36
+ bundle carries no runtime `fs` dependency.
37
+
38
+ `buildPrompt(result, resolveModel?, output?)` selects the state's template,
39
+ builds a view-model (model string, tail partial name, context), renders it,
40
+ collapses runs of three or more blank lines to two, and ensures exactly one
41
+ trailing newline. It throws for the five states that render no prompt at all
42
+ (`testing`, `planning`, `close-package`, `done`, `health-check`) — those are
43
+ performed entirely by the edge.
44
+
45
+ `npm run dev` runs `src/main.ts` directly via Node's native TypeScript
46
+ type-stripping (requires Node 22.6+). It registers `dev/hooks.mjs`, which fills
47
+ the two gaps the tsdown build otherwise covers: resolving `./Foo.js` specifiers
48
+ to the on-disk `./Foo.ts`, and importing `*.md` prompt files as text. Pass CLI
49
+ args after `--`, e.g. `npm run dev -- format <file>`.
50
+
51
+ The decision core (`src/Machine.ts`) is pure and IO-free, so the whole state
52
+ ladder and the counter folds are trivially unit-testable in isolation; all
53
+ git/filesystem IO is confined to the edge (`src/Events.ts`).
54
+
55
+ `npm run build` produces `dist/gtd.bundle.mjs`, which npm exposes as the `gtd`
56
+ binary via the `bin` field in `package.json`.
57
+
58
+ ## Mutation testing
59
+
60
+ Run mutation testing on-demand with `npm run test:mutation` (StrykerJS, ~2 min)
61
+ — never run it as part of routine development; it is a deliberate,
62
+ manually-triggered check. The single `stryker.config.json` mutates six core
63
+ files:
64
+
65
+ ```
66
+ src/Machine.ts src/Prompt.ts src/Config.ts
67
+ src/Format.ts src/State.ts src/Events.ts
68
+ ```
69
+
70
+ `src/Git.ts` is excluded: the Cucumber harness stubs git at the Effect boundary,
71
+ so `Git.ts` mutants have zero in-memory coverage.
72
+
73
+ The HTML report lands in `reports/mutation/mutation.html` (git-ignored).
74
+
75
+ ## Releasing
76
+
77
+ Releases are automatic. Push releasable Conventional Commits (`fix:`, `feat:`,
78
+ or breaking changes) to `main` and the Release workflow runs the tests, then
79
+ `npx semantic-release`. Semantic-release computes the next version, writes it
80
+ into `package.json`, builds the bundle, commits the bump back as
81
+ `chore(release): X.Y.Z [skip ci]`, tags `vX.Y.Z`, and creates the GitHub release
82
+ with `gtd.bundle.mjs` attached.
package/docs/loop.md ADDED
@@ -0,0 +1,104 @@
1
+ # Driving the loop
2
+
3
+ gtd splits what used to be one mutating command into three:
4
+
5
+ - **`gtd step`** — advance the workflow as the **human** actor, to fixpoint.
6
+ - **`gtd step-agent`** — advance the workflow as the **agent** actor, to
7
+ fixpoint.
8
+ - **`gtd next`** — print the prompt for whichever actor is currently awaited,
9
+ without mutating anything.
10
+
11
+ An agent loop is a two-beat protocol repeated forever:
12
+
13
+ 1. Run `gtd step-agent` to advance any agent-owned bookkeeping to a fixpoint.
14
+ 2. Run `gtd next --json` and read the `actor` field. If it is `"human"`,
15
+ **halt** — the human owns the next move, and the agent's job is done for this
16
+ turn. If it is `"agent"`, feed `prompt` (when non-null) to the agent, let it
17
+ act, then go back to step 1; at a pending checkpoint (`prompt` is null) go
18
+ straight back to step 1.
19
+
20
+ A human acts by editing files (answering questions in `.gtd/TODO.md` or
21
+ `.gtd/ARCHITECTURE.md`, annotating `.gtd/REVIEW.md`, fixing code) and then
22
+ running `gtd step` to capture the edit as their turn and hand control back to
23
+ the agent side of the loop.
24
+
25
+ ```bash
26
+ gtd step-agent # advance the machine's own bookkeeping
27
+ gtd next --json # ask who's up and what they should do
28
+ ```
29
+
30
+ See [`skills/loop/SKILL.md`](../skills/loop/SKILL.md) for the agent-facing
31
+ instructions that follow the same pinned contract. `gtd-loop`, installed
32
+ alongside `gtd` (see below), is the packaged, ready-to-run implementation of
33
+ that same script for anyone who doesn't want to drive the loop by hand.
34
+
35
+ ## The reference loop driver
36
+
37
+ A minimal bash implementation of the pinned two-beat protocol, driving an agent
38
+ CLI (e.g. `claude -p`) against `gtd --json` output. This is the authoritative
39
+ reference for what a loop driver must do; keep any other implementation
40
+ (including `skills/loop/SKILL.md`) consistent with it rather than editing both
41
+ independently.
42
+
43
+ ```bash
44
+ #!/usr/bin/env bash
45
+ set -euo pipefail
46
+
47
+ while true; do
48
+ # 1. Advance the machine's own agent-owned bookkeeping to a fixpoint.
49
+ gtd step-agent --json >/dev/null || true
50
+
51
+ # 2. Ask who's up next. `actor` is the single "proceed" signal.
52
+ next="$(gtd next --json)"
53
+ actor="$(jq -r .actor <<<"$next")"
54
+ prompt="$(jq -r .prompt <<<"$next")"
55
+
56
+ if [[ "$actor" != "agent" ]]; then
57
+ echo "Halting — the human owns the next move."
58
+ break
59
+ fi
60
+
61
+ if [[ "$prompt" == "null" ]]; then
62
+ # Agent-driven pending checkpoint: nothing to act on — loop back to
63
+ # step 1, whose `gtd step-agent` resumes the mid-chain bookkeeping.
64
+ continue
65
+ fi
66
+
67
+ # Agent's turn: feed the prompt to the agent, then let it finish with
68
+ # `gtd step-agent` itself (the prompt's tail instructs it to).
69
+ claude -p "$prompt" --dangerously-skip-permissions
70
+ done
71
+ ```
72
+
73
+ The agent is expected to run `gtd step-agent` itself once it finishes acting on
74
+ the prompt (the plain-mode tail says exactly this) — the driver's own
75
+ `step-agent` calls exist to advance any bookkeeping the agent doesn't own
76
+ (routing commits, test runs) between agent turns.
77
+
78
+ The loop halts on `actor: "human"` alone: a human rest (`pending: false`, the
79
+ prompt body addresses the human) or a human-driven pending checkpoint
80
+ (`pending: true`, resumed by the human's own `gtd step`). Everything the agent
81
+ side can drive — agent rests and agent-driven checkpoints — reports
82
+ `actor: "agent"`, so multiple agent turns and commits (e.g. successive test/fix
83
+ cycles, a force-approved package close) chain without human involvement until an
84
+ actual human gate is hit.
85
+
86
+ `bin/gtd-loop`, installed as the `gtd-loop` binary, is the packaged
87
+ implementation of this exact script — kept in sync with it the same way
88
+ `skills/loop/SKILL.md` is. It additionally attempts `gtd step` (not just
89
+ `gtd step-agent`) every iteration, so a plain rerun after you've edited a file
90
+ at a human gate (no commit needed) picks up your edit and keeps going, and it
91
+ halts with a diagnostic if the same state and prompt repeat with no progress
92
+ (see `skills/loop/SKILL.md`'s "Stall detection").
93
+
94
+ ## Using a different agent
95
+
96
+ `gtd-loop` defaults to
97
+ `claude -p "$GTD_LOOP_PROMPT" --dangerously-skip-permissions`, but the agent
98
+ invocation is swappable: set `GTD_LOOP_AGENT_CMD` to any shell command, and it
99
+ runs with the prompt available as `$GTD_LOOP_PROMPT` in its environment. For
100
+ example, to drive a different agent CLI:
101
+
102
+ ```bash
103
+ GTD_LOOP_AGENT_CMD='my-agent-cli --prompt "$GTD_LOOP_PROMPT"' gtd-loop
104
+ ```