@pmelab/gtd 2.7.0 → 3.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/docs/cli.md ADDED
@@ -0,0 +1,255 @@
1
+ # CLI reference
2
+
3
+ ```
4
+ Usage: gtd [command] [options]
5
+
6
+ Commands:
7
+ step <actor> Authenticate as <actor>, match the resolved rest's
8
+ declared patterns against the pending changes, and commit
9
+ (or squash) the one resulting transition
10
+ next Print the resolved rest's rendered script/prompt/message
11
+ (no mutation)
12
+ run Execute the resolved rest's emitted script, then step its
13
+ actor (the built-in script driver)
14
+ status Print the resolved rest's state/actor and which declared
15
+ pattern (if any) each pending change matches (no mutation)
16
+ format <file> Format a markdown file in place
17
+ lsp Start the LSP server for .gtd/ steering files (stdio)
18
+
19
+ Options:
20
+ --json Output structured JSON instead of plain text
21
+ --version, -v Print version and exit
22
+ --help, -h Print this help and exit
23
+ ```
24
+
25
+ `--version` (`-v`) and `--help` (`-h`) short-circuit before any git or
26
+ repository-state work — they run outside a repo and in any repo state. Bare
27
+ `gtd` (no subcommand) is a usage error: it prints the help text and exits 1
28
+ without touching the repository. Every other command must be run from the
29
+ **repository root** — gtd derives the workflow, pending changes, and process
30
+ history relative to cwd, so it refuses with a clear error if invoked from a
31
+ subdirectory.
32
+
33
+ `--json` is the only long option. Any other `--` option (including a typo like
34
+ `--jsn`) is rejected with a usage error rather than silently ignored, so a
35
+ mistyped flag can never degrade a JSON caller to plain-text mode.
36
+
37
+ ## `gtd step <actor>`
38
+
39
+ Authenticates `<actor>` against the resolved rest and performs the ONE resulting
40
+ transition. Unlike v2, there is no fixpoint chain to drive: the pattern
41
+ machine's `on` edges are direct one-hop transitions, so a single invocation
42
+ authors at most one commit (a normal turn) or performs one squash
43
+ (§[STATES.md](../STATES.md#8-the-squash-lifecycle)) — never both, never a chain
44
+ of several. A caller that wants several transitions issues several invocations.
45
+
46
+ - **Out-of-turn refusal** — `<actor>` isn't the resolved state's declared actor:
47
+ exit non-zero, zero commits.
48
+ `gtd step <actor>: out of turn — "<state>" awaits <awaited-actor>`
49
+ - **No-match refusal** — the tree is dirty and no declared `on` pattern matches:
50
+ exit non-zero, zero commits, naming every declared pattern.
51
+ `gtd step <actor>: no declared pattern matches the pending changes at "<state>" — declared patterns: <p1>, <p2>, …`
52
+ - **No-op** — the tree is clean and the state declares no `C` pattern: exit
53
+ **0**, zero commits. This is the default, silent case a loop driver relies on
54
+ (see [Driving the loop](loop.md)).
55
+ - **Commit/squash** — a pattern matched: exit 0, one commit (or one squash)
56
+ authored.
57
+
58
+ Plain-mode output is one line:
59
+
60
+ ```
61
+ committed: gtd(human): grilling
62
+ ```
63
+
64
+ or, at a no-op:
65
+
66
+ ```
67
+ nothing to do at "idle"
68
+ ```
69
+
70
+ `--json` emits `{state, subject}` — `subject` is `null` at a no-op:
71
+
72
+ ```json
73
+ { "state": "grilling", "subject": "gtd(human): grilling" }
74
+ ```
75
+
76
+ ## `gtd next [--json]`
77
+
78
+ Pure emitter of the resolved rest's rendered content — it **never mutates** the
79
+ repository (no commits, no file writes, no script execution). Resolves HEAD
80
+ exactly like `gtd step`, renders that state's declared
81
+ `script`/`prompt`/`message` template, and prints it. `kind` is never `"commit"`
82
+ here: resolution never rests at a commit state (entering one always ends the
83
+ process in the same step that entered it — see
84
+ [STATES.md §5](../STATES.md#5-resolution)).
85
+
86
+ Plain mode prints the rendered content verbatim (with exactly one trailing
87
+ newline) — never the `model`/`file`/`mode` hints, which are JSON-only. `--json`
88
+ emits `{state, actor, kind, content, model?, file?, mode?}`:
89
+
90
+ ```json
91
+ {
92
+ "state": "building",
93
+ "actor": "agent",
94
+ "kind": "prompt",
95
+ "content": "You are an autonomous coding agent. ..."
96
+ }
97
+ ```
98
+
99
+ - `state` — the resolved state.
100
+ - `actor` — the state's declared actor.
101
+ - `kind` — `"script"` | `"prompt"` | `"message"` — the dispatch key a driver
102
+ switches on (see [Driving the loop](loop.md)).
103
+ - `content` — the fully rendered template.
104
+ - `model` — the state's opaque `model:` hint, RENDERED through the same template
105
+ context as `content` (see
106
+ [Configuration](configuration.md#model--the-opaque-harness-hint-template-rendered)),
107
+ present only when the state declares one; **omitted entirely** (never `null`)
108
+ when unset.
109
+ - `file` — the state's declared steering file, RENDERED the same way; `mode` —
110
+ its format, verbatim (`"qa"` | `"review"`) (see
111
+ [Configuration](configuration.md#filemode--the-steering-file-association)).
112
+ Both present only when the state declares them; **omitted entirely** (never
113
+ `null`) otherwise.
114
+
115
+ ## `gtd run`
116
+
117
+ The built-in driver for a `script`-content rest — the **only** place gtd itself
118
+ spawns a subprocess. Renders the resolved rest exactly like `gtd next`, executes
119
+ its content verbatim via `bash -c` (foreground, inherited stdio, exit code
120
+ deliberately ignored — a check script encodes its outcome in the tree, e.g.
121
+ writing a findings file, never in its exit status), then runs `gtd step <actor>`
122
+ for that state's own actor to capture the outcome in one command. Refuses (exit
123
+ non-zero, no execution, no step) when the resolved rest isn't a script:
124
+
125
+ ```
126
+ gtd run: "<state>" awaits a <kind> from "<actor>" — nothing scripted to run
127
+ ```
128
+
129
+ Reports the same `{state, subject}` (or `committed: …` / `nothing to do at "…"`)
130
+ as `gtd step` for the capturing step it performs. Takes no arguments — extra
131
+ positional args are rejected.
132
+
133
+ ## `gtd status [--json]`
134
+
135
+ Pure, read-only dry-run reporter — the same resolution `gtd next` performs, but
136
+ reporting the resolved state/actor and, for every pending change, which declared
137
+ `on` pattern (if any) matches it — no mutation, and no CONTENT rendering (the
138
+ `script`/`prompt`/`message`/`commit` template is never rendered here, unlike
139
+ `gtd next`). It DOES render the resolved state's `model:`/`file:` hints (if
140
+ declared) through the same `it.vars`-carrying template context `gtd next` uses —
141
+ see
142
+ [Configuration](configuration.md#model--the-opaque-harness-hint-template-rendered)
143
+ — so a templated `model:`/`file:` failing to render fails `gtd status` too,
144
+ exactly like it would fail `gtd next`.
145
+
146
+ ```
147
+ State: working
148
+ Awaits: agent
149
+ Pending:
150
+ A DONE.md -> A DONE.md
151
+ A scratch.txt -> (no match)
152
+ ```
153
+
154
+ or, on a clean tree: `Pending: (clean)`. A `Model: <value>` line appears right
155
+ after `Awaits:` when the resolved state declares a `model:` hint, and
156
+ `File: <value>`/`Mode: <value>` lines appear after that (in that order) when
157
+ declared — each independently, only when set.
158
+
159
+ `--json` emits
160
+ `{state, actor, changes: [{status, path, pattern}], model?, file?, mode?}` —
161
+ `pattern` is `null` when no declared row matches that change; `model`/`file`/
162
+ `mode` are present only when the resolved state declares them (omitted entirely,
163
+ never `null`, otherwise):
164
+
165
+ ```json
166
+ {
167
+ "state": "working",
168
+ "actor": "agent",
169
+ "changes": [
170
+ { "status": "A", "path": "DONE.md", "pattern": "A DONE.md" },
171
+ { "status": "A", "path": "scratch.txt", "pattern": null }
172
+ ]
173
+ }
174
+ ```
175
+
176
+ `gtd status` takes no arguments — extra positional args are rejected.
177
+
178
+ ## `gtd format <file>`
179
+
180
+ Formats a markdown file in place with a bundled prettier (`parser: "markdown"`,
181
+ `printWidth: 80`, `proseWrap: "always"`), ignoring the host repo's own
182
+ `.prettierrc` so `.gtd/`-tracked files stay consistently formatted regardless of
183
+ the host project's toolchain. Rejects `--json` (exit 1,
184
+ `gtd format does not accept --json`) — it's a plain file operation, not a state
185
+ command.
186
+
187
+ Errors (all exit 1, message on stderr):
188
+
189
+ - Missing path: `gtd format: missing file path argument`
190
+ - Extra arguments: `gtd format: too many arguments — expected one path, got: …`
191
+ - Non-markdown file:
192
+ `gtd format: <file> is not a markdown file (expected .md or .markdown)`
193
+ - File not found: `gtd: skipped formatting <file>: not found`
194
+
195
+ ## `gtd lsp`
196
+
197
+ Starts an LSP server over stdio for `.gtd/` steering files — document symbols
198
+ for a `qa`-mode file's open questions and a `review`-mode file's review
199
+ chunks/hunks, code actions to check/uncheck a hunk or a whole chunk, and
200
+ diagnostics publishing the same parser findings the bundled workflow's
201
+ `.gtd/FORMAT.md` validators produce (see `src/OpenQuestions.ts` /
202
+ `src/ReviewDoc.ts` and
203
+ [STATES.md §10](../STATES.md#10-the-bundled-default-workflow)).
204
+
205
+ **Config-driven** (see
206
+ [docs/design/state-file-association.md](design/state-file-association.md)): the
207
+ server locates the active gtd config the same way the CLI does, from the
208
+ `initialize` request's workspace root (falling back to the open document's own
209
+ directory), renders every state's `file:` into an absolute-path → `mode` map,
210
+ and dispatches on it — first declaring state wins a path conflict. A path the
211
+ map doesn't cover (or no config at all) falls back to the basename dispatch
212
+ (`TODO.md` → `qa`, `REVIEW.md` → `review`), so the server still works standalone
213
+ with no `.gtdrc` in sight. Also registers an `executeCommand`,
214
+ `gtd.openSteeringFile`: resolves the current state exactly like `gtd status`
215
+ (config + git HEAD) and asks the client to show its `file:`
216
+ (`window/showDocument`); a state with no `file:` gets an informational message
217
+ naming the state instead — bind it to an editor keybinding for a "jump to the
218
+ active steering file" command.
219
+
220
+ Dispatched before the repository-root guard and auto-init, like `gtd format`.
221
+ Rejects `--json` (exit 1, `gtd lsp does not accept --json`) and extra positional
222
+ arguments — it's a long-running server, not a state command. Runs until the
223
+ client disconnects (the LSP `exit` notification), then exits cleanly.
224
+
225
+ ## Error envelope
226
+
227
+ Every command, in `--json` mode, reports a failure as a machine-readable
228
+ envelope on **stdout**, and still exits 1:
229
+
230
+ ```json
231
+ { "state": "error", "prompt": "<message>" }
232
+ ```
233
+
234
+ A human-readable `gtd: <message>` line is still written to **stderr** regardless
235
+ of `--json` — the envelope adds a structured stdout channel, it does not replace
236
+ the plain-text one.
237
+
238
+ ## Repository requirements
239
+
240
+ - **Single writer, linear branch.** A process's history is walked via
241
+ **first-parent** commits only.
242
+ - **Test/build artifacts must be gitignored.** This is **load-bearing**, not a
243
+ style preference: every step decision detects "clean" via
244
+ `git diff --name-status HEAD` (tracked changes) unioned with
245
+ `git ls-files --others --exclude-standard` (untracked files), which silently
246
+ omits anything matched by `.gitignore`. If a `script` state's command (or the
247
+ build it triggers) writes tracked-but-untracked output — a `dist/`, a coverage
248
+ report, a log file — into the working tree, the tree never goes clean after a
249
+ green run, and the check's `"C"` pattern never fires. Gitignore every path
250
+ your scripts write before wiring gtd into a repo.
251
+ - **Repository root invocation.** Every state subcommand (`step`/`next`/`run`/
252
+ `status`) must run from the git repository root — the workflow, pending
253
+ changes, and process history are resolved against the process cwd.
254
+ `--help`/`--version`, `format`, and `lsp` skip this guard entirely (and any
255
+ git/`.gtdrc` dependency along with it).
@@ -0,0 +1,377 @@
1
+ # Configuration
2
+
3
+ gtd reads an optional `.gtdrc` config file via
4
+ [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no config, the
5
+ bundled default workflow applies (see
6
+ [STATES.md](../STATES.md#10-the-bundled-default-workflow)). Supported filenames
7
+ (searched in this order):
8
+
9
+ - `.gtdrc`
10
+ - `.gtdrc.json`
11
+ - `.gtdrc.yaml`
12
+ - `.gtdrc.yml`
13
+ - `gtd.config.json`
14
+ - `gtd.config.yaml`
15
+
16
+ ## Schema
17
+
18
+ v3's `.gtdrc` has exactly two blessed top-level keys:
19
+
20
+ - **`workflow`** (object, optional) — the whole machine definition, compiled by
21
+ `src/PatternConfig.ts`. Absent = the bundled default workflow. See
22
+ ["The `workflow:` key" below](#the-workflow-key) for its schema.
23
+ - **`vars`** (object, optional) — a flat `name -> scalar` map, one layer of the
24
+ merged `it.vars` every template sees — see ["Variables"](#variables) below.
25
+ - **`$schema`** (string, optional) — stripped before validation, so it never
26
+ counts as an unknown key. Point it at the published schema for editor-backed
27
+ autocompletion. A `schema.json` is generated from `src/ConfigSchema.ts` at
28
+ build time and ships with the package.
29
+
30
+ Any other top-level key is **rejected** (`onExcessProperty: "error"`) — v3 has
31
+ no `testCommand`, `fixAttemptCap`, `reviewThreshold`, `agenticReview`, `squash`,
32
+ `learning`, `decisionLog`, or `models` keys; all of that machinery is gone (see
33
+ [Upgrading](upgrading.md)). The engine blesses no VARIABLE NAMES either —
34
+ `testCommand` (the bundled default workflow's own var, see
35
+ ["Variables"](#variables)) is workflow-authored data like any other `it.vars`
36
+ entry, not a special key gtd interprets.
37
+
38
+ ## The `workflow:` key
39
+
40
+ The `workflow:` key is the **only** definition source — there is no `extends`,
41
+ no merge-over-a-built-in; the bundled default workflow is itself a YAML asset
42
+ compiled through the exact same compiler (`src/workflows/default.yaml` →
43
+ `compileWorkflowConfig`). Its shape:
44
+
45
+ ```yaml
46
+ workflow:
47
+ vars: # optional — the workflow's own declared `it.vars` defaults (see "Variables" below)
48
+ anyKey: anyScalarValue
49
+ states:
50
+ <name>:
51
+ actor: <string> # forbidden on a commit state, required otherwise
52
+ script: <string> # exactly one of script/prompt/message/commit
53
+ prompt: <string>
54
+ message: <string>
55
+ commit: <string>
56
+ on: # a mapping, DECLARATION ORDER PRESERVED
57
+ "<pattern>": <targetState>
58
+ initial: true # exactly one state across the whole workflow
59
+ retry:
60
+ max: <number>
61
+ otherwise: <targetState>
62
+ model: <string> # optional, opaque harness hint — forbidden on a commit state
63
+ file: <string> # optional, an Eta template naming the state's steering file — forbidden on a commit state
64
+ mode: qa | review # optional, requires "file" — forbidden on a commit state
65
+ ```
66
+
67
+ See [STATES.md](../STATES.md#1-the-model) for what each field means to the
68
+ engine, and [§3](../STATES.md#3-pattern-grammar) for the pattern grammar.
69
+
70
+ ### Content values: inline or a file reference
71
+
72
+ A `script`/`prompt`/`message`/`commit` value starting with `./` or `../` is a
73
+ **file reference** — resolved relative to the config file's own directory and
74
+ read at load time. A missing or unreadable file reference is a **load error**,
75
+ collected and thrown with every other config problem — never silently treated as
76
+ inline text. Any other string (including one that merely contains a `/`, or an
77
+ absolute path) is inline Eta template source, used verbatim.
78
+
79
+ ```yaml
80
+ workflow:
81
+ states:
82
+ working:
83
+ actor: agent
84
+ prompt: ./prompts/working.md # read from alongside this .gtdrc
85
+ on:
86
+ "* **": done
87
+ ```
88
+
89
+ A `vars:` key sibling to `states:` inside `workflow:` declares the workflow's
90
+ own defaults for `it.vars` — see ["Variables"](#variables) below for the full
91
+ three-layer picture.
92
+
93
+ ### `model:` — the opaque harness hint, template-rendered
94
+
95
+ A state may declare `model: <string>` — an OPAQUE label (e.g. `smart`, `fast`,
96
+ or a concrete model id) gtd never interprets; it is only passed through so the
97
+ driving loop can map it onto whatever models its agent harness provides. Unset
98
+ means "use the harness's default." Forbidden on a commit state (never at rest,
99
+ emits nothing):
100
+
101
+ ```yaml
102
+ workflow:
103
+ states:
104
+ working:
105
+ actor: agent
106
+ model: smart
107
+ prompt: do the thing
108
+ on:
109
+ "* **": done
110
+ ```
111
+
112
+ Like every content string, `model:` is rendered as an Eta template through the
113
+ exact same context as the state's content — a plain string with no Eta tags
114
+ (`smart` above) passes through unchanged, but
115
+ `model: "<%= it.vars.reviewModel %>"` resolves against the merged `it.vars` (see
116
+ ["Variables"](#variables)). A render failure behaves exactly like a content
117
+ render failure at the same call site: `gtd next`/`gtd status` error out, nothing
118
+ committed.
119
+
120
+ `gtd next --json` and `gtd status --json` include a `"model"` key (the RENDERED
121
+ value) only when the resolved state declares one — it is **omitted entirely**,
122
+ never emitted as `null`, when unset.
123
+
124
+ ### `file:`/`mode:` — the steering-file association
125
+
126
+ A state may additionally declare `file:` — an Eta template naming THE steering
127
+ file this state is about: the file a human/editor should look at while the
128
+ machine rests here (rendered through the same `it.vars`-carrying context as
129
+ content/`model`; must render non-empty). Forbidden on a commit state (never at
130
+ rest). Multiple states may share one `file:` (and, in the bundled default, do):
131
+
132
+ ```yaml
133
+ workflow:
134
+ vars:
135
+ planFile: .gtd/PLAN.md
136
+ states:
137
+ working:
138
+ actor: agent
139
+ file: <%= it.vars.planFile %>
140
+ mode: qa
141
+ prompt: develop the plan
142
+ on:
143
+ "* **": done
144
+ ```
145
+
146
+ `mode:` requires a sibling `file:` and names the file's FORMAT, from a closed,
147
+ documented vocabulary:
148
+
149
+ | `mode` | Format |
150
+ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
151
+ | `qa` | The open-questions format (`## Open Questions`, one `###` sub-heading per question, `Suggested default: ...`/`Answer: ...`). |
152
+ | `review` | The checkbox review format (`# Review: <hash>` header, `<!-- base: <hash> -->` comment, `##` chunks, `- [ ]` pointers). |
153
+
154
+ An unknown `mode` value is a load error naming the allowed values (typos must
155
+ not silently disable editor support) — like `model`, the ENGINE never branches
156
+ on `mode`; only `gtd lsp` (see
157
+ [docs/design/state-file-association.md](design/state-file-association.md))
158
+ interprets it, to decide which document symbols/code actions/diagnostics a file
159
+ gets.
160
+
161
+ `gtd next --json`/`gtd status --json` gain `"file"` (the RENDERED path) and
162
+ `"mode"` (verbatim) keys, each **omitted entirely** (never `null`) when the
163
+ resolved state declares none — exactly like `model`. Plain `gtd status` prints
164
+ `File:`/`Mode:` lines (right after `Model:`, when present) when set.
165
+
166
+ **Known limitation — `on` pattern keys are NOT Eta templates.** A workflow's
167
+ `on` patterns keep LITERAL `.gtd/…` paths, so repointing a filename var
168
+ (`.gtdrc`'s top-level `vars:`, or a `GTD_VAR_` override) without ALSO overriding
169
+ the workflow's `on` patterns desyncs the machine: `file:` (and any template
170
+ reading/writing that path) follows the var, but the `on` map that decides what a
171
+ change to that path MEANS keeps matching the old literal path. The vars are a
172
+ DRY mechanism inside templates and the state↔file association, not a rename
173
+ switch. (Making pattern keys var-aware at compile time is possible future work.)
174
+
175
+ ### Template variables
176
+
177
+ Every `script`/`prompt`/`message`/`commit`/`model` template is rendered as an
178
+ Eta template (`it.<name>`) with:
179
+
180
+ | Variable | Meaning |
181
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | `startCommit` | The hash the current process started from (before its first turn). |
183
+ | `currentCommit` | HEAD's hash at render time. |
184
+ | `previousCommit` | The hash before the last transition (HEAD's parent, in-process). |
185
+ | `state` | The state whose content is being rendered. |
186
+ | `actor` | The actor this render is for. |
187
+ | `processDiff` | `startCommit..HEAD` plus the pending working-tree diff. |
188
+ | `lastDiff` | The diff of the last transition alone. |
189
+ | `read(path)` | Reads a working-tree file (pending contents, not HEAD's) by repo-relative path. Throws for a missing/unreadable path — for a `commit:` template, that throw refuses the step (see [STATES.md §8](../STATES.md#8-the-squash-lifecycle)). |
190
+ | `vars` | The merged three-layer variable map, always a flat `Record<string, string>` — see ["Variables"](#variables) below. |
191
+
192
+ ## Variables
193
+
194
+ Every template — `script`/`prompt`/`message`/`commit`, and now `model` — sees
195
+ `it.vars`: a flat `Record<string, string>` assembled from three layers, **later
196
+ wins**:
197
+
198
+ 1. **The workflow's own `vars:` key** (sibling to `states:`, shown above) — the
199
+ workflow author's declared defaults. The bundled default workflow declares
200
+ `vars: { testCommand: "npm test" }`, read by `checking`'s script as
201
+ `<%~ it.vars.testCommand %>` (see
202
+ [STATES.md §10](../STATES.md#10-the-bundled-default-workflow)).
203
+ 2. **A top-level `.gtdrc` `vars:` key** (a sibling of `workflow:`, NOT nested
204
+ inside it) — per-repo tuning without redefining the whole workflow. Subject
205
+ to the same cwd→home deep merge as everything else in `.gtdrc` (innermost
206
+ wins per-key).
207
+ 3. **`GTD_VAR_<name>` environment variables** — highest precedence, checked at
208
+ every invocation. The prefix is stripped and the REMAINING CASE matched
209
+ exactly: `GTD_VAR_testCommand` sets `testCommand`, not `TESTCOMMAND` or
210
+ `testcommand`. This is the only layer that may introduce a name neither
211
+ config layer declared.
212
+
213
+ Values in layers 1–2 must be YAML scalars (string/number/boolean) — coerced to
214
+ strings at load time; an object or array value is a load error, collected
215
+ alongside every other config-shape finding (see
216
+ ["Validation and errors"](#validation-and-errors)). Environment values are
217
+ already strings.
218
+
219
+ ```yaml
220
+ # .gtdrc — overriding the bundled default's testCommand
221
+ vars:
222
+ testCommand: npm run test:ci
223
+ ```
224
+
225
+ ```bash
226
+ # highest precedence — beats both the workflow default and the .gtdrc value above
227
+ GTD_VAR_testCommand="npm run test -- --bail" gtd run
228
+ ```
229
+
230
+ A template reads any of it as `it.vars.<name>`:
231
+
232
+ ```yaml
233
+ workflow:
234
+ vars:
235
+ reviewer: alice
236
+ states:
237
+ working:
238
+ actor: agent
239
+ prompt: "Assigned reviewer: <%= it.vars.reviewer %>"
240
+ model: "<%= it.vars.reviewModel %>" # a GTD_VAR_reviewModel override, or a .gtdrc vars: entry
241
+ on:
242
+ "* **": done
243
+ ```
244
+
245
+ No variable name is blessed by the engine — `testCommand` is workflow-authored
246
+ data like any other `it.vars` entry, not a special key gtd interprets; a custom
247
+ workflow is free to declare and read any names it likes.
248
+
249
+ ## Validation and errors
250
+
251
+ Config-shape problems (unknown keys, wrong types, unreadable file references)
252
+ are collected together; if the shape is clean, the assembled definition is
253
+ additionally run through the engine's own `validateDefinition` (see
254
+ [STATES.md §9](../STATES.md#9-validation)). A bad config throws **one** error
255
+ listing every finding, at load time — before anything touches the repository —
256
+ never partially, and never deferred to step time:
257
+
258
+ ```
259
+ workflow config:
260
+ - state "idle": must declare exactly one of script/prompt/message/commit (found 2)
261
+ - state "idle": "on" target "nowhere" is not a defined state
262
+ ```
263
+
264
+ Other load failures:
265
+
266
+ - **Parse errors** (malformed YAML/JSON) — message includes the offending
267
+ filename.
268
+ - **Non-object top-level** — a YAML list or `null` at the root is rejected with
269
+ the filename in the message.
270
+ - **Unknown top-level key** — anything besides `workflow`/`vars`/`$schema` emits
271
+ `Invalid gtd config: <field>: <reason>`.
272
+ - **A bad top-level `vars:` entry** — an object/array value fails the same way
273
+ as a bad workflow-level `vars:` entry (see ["Variables"](#variables)), one
274
+ aggregated error:
275
+ `gtd config:\n - "vars.<name>" must be a string, number, or boolean, got <type>`.
276
+
277
+ All of these exit **1** and write to **stderr**, never stdout.
278
+
279
+ ## Lookup and precedence
280
+
281
+ gtd walks from the current working directory **up to your home directory** (or
282
+ to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
283
+ finds along the way. All found levels are **deep-merged**, with the **innermost
284
+ (cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
285
+ directory cascades to every checkout beneath it, while any individual checkout
286
+ can still override with its own `.gtdrc`.
287
+
288
+ ## Auto-init
289
+
290
+ On every **state command** (`step`, `next`, `run`, `status`) that has passed the
291
+ repo-root guard, if the cwd→root walk finds **no** config anywhere, gtd creates
292
+ and commits a starter `.gtdrc.json` at the repository root containing only a
293
+ `$schema` link. Auto-init never runs for `--version`/`--help`, `format`,
294
+ bare/unknown commands, or an invocation refused by the repo-root guard — those
295
+ perform no repository mutation of any kind. On a repo with no commits yet, or
296
+ whose HEAD isn't a `gtd(actor): state` commit, the stub is committed as its own
297
+ `chore: add .gtdrc.json`. If HEAD is already a `gtd(actor): state` commit
298
+ (mid-process), the stub is instead **amended into HEAD** — stacking a fresh
299
+ boundary commit there would produce an unrecognized HEAD that resolves back to
300
+ the workflow's initial state.
301
+
302
+ ## A complete example
303
+
304
+ A three-state note-taking machine: draft, revise-or-accept, and a squashed
305
+ commit — one prompt/message pair reading a workflow-declared `it.vars` entry:
306
+
307
+ ```yaml
308
+ # .gtdrc.yaml
309
+ workflow:
310
+ vars:
311
+ reviewer: alice
312
+ states:
313
+ idle:
314
+ actor: human
315
+ initial: true
316
+ message: |
317
+ No active note. Write NOTE.md with what you want written up, then run
318
+ `gtd step human`.
319
+ on:
320
+ "* **": drafting
321
+
322
+ drafting:
323
+ actor: agent
324
+ prompt: |
325
+ Read NOTE.md and draft a short write-up in DRAFT.md.
326
+ Reviewer of record: <%= it.vars.reviewer %>.
327
+ Leave DRAFT.md uncommitted and finish your turn.
328
+ on:
329
+ "* **": revising
330
+
331
+ revising:
332
+ actor: human
333
+ message: |
334
+ DRAFT.md holds the draft. Edit it for another round, or run
335
+ `gtd step human` with no edits to accept it and write the commit
336
+ message.
337
+ on:
338
+ "C": working
339
+ "* **": drafting
340
+
341
+ working:
342
+ actor: agent
343
+ prompt: |
344
+ The draft is accepted. Write COMMIT_MSG.md with one conventional-
345
+ commits message for the whole cycle, then finish your turn.
346
+ on:
347
+ "A COMMIT_MSG.md": done
348
+ "M COMMIT_MSG.md": done
349
+
350
+ done:
351
+ commit: '<%~ it.read("COMMIT_MSG.md") %>'
352
+ ```
353
+
354
+ A clean `gtd step human` at `revising` (the `C` event) accepts the draft and
355
+ moves to `working`; any other pending change loops back to `drafting`. Writing
356
+ `COMMIT_MSG.md` at `working` enters `done`, which squashes every commit since
357
+ `idle` into one, using `COMMIT_MSG.md`'s content as the message, and discards
358
+ the file.
359
+
360
+ ## Recipe: a per-task builder loop without a script state
361
+
362
+ A `picking`-style queue arbiter (see
363
+ [docs/examples/advanced-workflow.md](examples/advanced-workflow.md)) is a
364
+ `script` state that turns a task-queue glob into a diff patterns can see. A
365
+ driver that can't run scripts can get the same per-task loop out of pure
366
+ prompt/`on` declarations instead, at the cost of trusting the agent to honor a
367
+ marker protocol rather than a deterministic `ls`: see
368
+ [docs/design/work-packages.md §3](design/work-packages.md) ("Option B —
369
+ agent-encoded verdict") for the worked example.
370
+
371
+ ## A fuller example: two-phase planning, task decomposition, agent-prepared review
372
+
373
+ The bundled default above is deliberately small. For a heavier machine — Q&A
374
+ planning loops, an architecture phase, task decomposition, the deterministic
375
+ per-task `picking` arbiter, and agent-prepared `.gtd/REVIEW.md` review — see
376
+ [docs/examples/advanced-workflow.md](examples/advanced-workflow.md), a
377
+ copy-paste-ready `.gtdrc` recipe.