@pmelab/gtd 2.8.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -40
- package/bin/gtd-loop +43 -23
- package/dist/gtd.bundle.mjs +2511 -3481
- package/docs/cli.md +222 -281
- package/docs/configuration.md +350 -71
- package/docs/design/configurable-state-machine.md +1439 -0
- package/docs/design/delta-purity-plan.md +283 -0
- package/docs/design/pattern-machine-plan.md +164 -0
- package/docs/design/scripted-actor.md +289 -0
- package/docs/design/state-file-association.md +112 -0
- package/docs/design/state-machine-validation.md +437 -0
- package/docs/design/steering-file-loops.md +164 -0
- package/docs/design/work-packages.md +194 -0
- package/docs/development.md +55 -36
- package/docs/examples/advanced-workflow.md +428 -0
- package/docs/loop.md +60 -60
- package/docs/upgrading.md +125 -0
- package/package.json +2 -2
- package/schema.json +89 -65
- package/docs/upgrading-from-v1.md +0 -45
- package/docs/workflow.md +0 -428
package/docs/configuration.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
gtd reads an optional `.gtdrc` config file via
|
|
4
4
|
[cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). With no config, the
|
|
5
|
-
|
|
5
|
+
bundled default workflow applies (see
|
|
6
|
+
[STATES.md](../STATES.md#10-the-bundled-default-workflow)). Supported filenames
|
|
7
|
+
(searched in this order):
|
|
6
8
|
|
|
7
9
|
- `.gtdrc`
|
|
8
10
|
- `.gtdrc.json`
|
|
@@ -13,62 +15,281 @@ built-in defaults apply. Supported filenames (searched in this order):
|
|
|
13
15
|
|
|
14
16
|
## Schema
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **`
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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**.
|
|
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.
|
|
52
25
|
- **`$schema`** (string, optional) — stripped before validation, so it never
|
|
53
26
|
counts as an unknown key. Point it at the published schema for editor-backed
|
|
54
|
-
autocompletion. A `schema.json` is generated from
|
|
55
|
-
time and ships with the package.
|
|
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.
|
|
56
248
|
|
|
57
249
|
## Validation and errors
|
|
58
250
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
`validateDefinition`'s findings include the **semantic graph checks**: every
|
|
265
|
+
`on` target and `retry.otherwise` must name a defined state, and every state
|
|
266
|
+
must be **reachable** from the initial state by walking `on` targets and
|
|
267
|
+
`retry.otherwise` redirects — an unreachable state (typically a typo'd rename or
|
|
268
|
+
a leftover) is a load error like any other, since a workflow is bound to a
|
|
269
|
+
project and edited as a project-wide change.
|
|
270
|
+
|
|
271
|
+
Many of these problems never reach gtd at all if your editor validates against
|
|
272
|
+
the published schema: `schema.json` fully types the `workflow:` key (state
|
|
273
|
+
shape, content kinds, `on`/`retry` structure, the `mode` vocabulary), so a
|
|
274
|
+
yaml-language-server-style editor flags unknown keys and wrong types as you
|
|
275
|
+
type. The rules JSON Schema cannot express — exactly one content kind, exactly
|
|
276
|
+
one `initial: true`, targets naming defined states, reachability — remain the
|
|
277
|
+
compiler's job at load time.
|
|
278
|
+
|
|
279
|
+
Other load failures:
|
|
61
280
|
|
|
62
281
|
- **Parse errors** (malformed YAML/JSON) — message includes the offending
|
|
63
282
|
filename.
|
|
64
283
|
- **Non-object top-level** — a YAML list or `null` at the root is rejected with
|
|
65
284
|
the filename in the message.
|
|
66
|
-
- **
|
|
285
|
+
- **Unknown top-level key** — anything besides `workflow`/`vars`/`$schema` emits
|
|
67
286
|
`Invalid gtd config: <field>: <reason>`.
|
|
68
|
-
- **
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
287
|
+
- **A bad top-level `vars:` entry** — an object/array value fails the same way
|
|
288
|
+
as a bad workflow-level `vars:` entry (see ["Variables"](#variables)), one
|
|
289
|
+
aggregated error:
|
|
290
|
+
`gtd config:\n - "vars.<name>" must be a string, number, or boolean, got <type>`.
|
|
291
|
+
|
|
292
|
+
All of these exit **1** and write to **stderr**, never stdout.
|
|
72
293
|
|
|
73
294
|
## Lookup and precedence
|
|
74
295
|
|
|
@@ -77,37 +298,95 @@ to the filesystem root when cwd is outside home), collecting every `.gtdrc` it
|
|
|
77
298
|
finds along the way. All found levels are **deep-merged**, with the **innermost
|
|
78
299
|
(cwd) config winning** on conflicts — so a shared `.gtdrc` in a worktree-parent
|
|
79
300
|
directory cascades to every checkout beneath it, while any individual checkout
|
|
80
|
-
can still override
|
|
301
|
+
can still override with its own `.gtdrc`.
|
|
81
302
|
|
|
82
303
|
## Auto-init
|
|
83
304
|
|
|
84
|
-
On every **state command** (`step`, `
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(mid-
|
|
93
|
-
boundary commit there would produce an unrecognized HEAD
|
|
94
|
-
|
|
305
|
+
On every **state command** (`step`, `next`, `run`, `status`) that has passed the
|
|
306
|
+
repo-root guard, if the cwd→root walk finds **no** config anywhere, gtd creates
|
|
307
|
+
and commits a starter `.gtdrc.json` at the repository root containing only a
|
|
308
|
+
`$schema` link. Auto-init never runs for `--version`/`--help`, `format`,
|
|
309
|
+
bare/unknown commands, or an invocation refused by the repo-root guard — those
|
|
310
|
+
perform no repository mutation of any kind. On a repo with no commits yet, or
|
|
311
|
+
whose HEAD isn't a `gtd(actor): state` commit, the stub is committed as its own
|
|
312
|
+
`chore: add .gtdrc.json`. If HEAD is already a `gtd(actor): state` commit
|
|
313
|
+
(mid-process), the stub is instead **amended into HEAD** — stacking a fresh
|
|
314
|
+
boundary commit there would produce an unrecognized HEAD that resolves back to
|
|
315
|
+
the workflow's initial state.
|
|
95
316
|
|
|
96
|
-
##
|
|
317
|
+
## A complete example
|
|
318
|
+
|
|
319
|
+
A three-state note-taking machine: draft, revise-or-accept, and a squashed
|
|
320
|
+
commit — one prompt/message pair reading a workflow-declared `it.vars` entry:
|
|
97
321
|
|
|
98
322
|
```yaml
|
|
99
323
|
# .gtdrc.yaml
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
324
|
+
workflow:
|
|
325
|
+
vars:
|
|
326
|
+
reviewer: alice
|
|
110
327
|
states:
|
|
111
|
-
|
|
112
|
-
|
|
328
|
+
idle:
|
|
329
|
+
actor: human
|
|
330
|
+
initial: true
|
|
331
|
+
message: |
|
|
332
|
+
No active note. Write NOTE.md with what you want written up, then run
|
|
333
|
+
`gtd step human`.
|
|
334
|
+
on:
|
|
335
|
+
"* **": drafting
|
|
336
|
+
|
|
337
|
+
drafting:
|
|
338
|
+
actor: agent
|
|
339
|
+
prompt: |
|
|
340
|
+
Read NOTE.md and draft a short write-up in DRAFT.md.
|
|
341
|
+
Reviewer of record: <%= it.vars.reviewer %>.
|
|
342
|
+
Leave DRAFT.md uncommitted and finish your turn.
|
|
343
|
+
on:
|
|
344
|
+
"* **": revising
|
|
345
|
+
|
|
346
|
+
revising:
|
|
347
|
+
actor: human
|
|
348
|
+
message: |
|
|
349
|
+
DRAFT.md holds the draft. Edit it for another round, or run
|
|
350
|
+
`gtd step human` with no edits to accept it and write the commit
|
|
351
|
+
message.
|
|
352
|
+
on:
|
|
353
|
+
"C": working
|
|
354
|
+
"* **": drafting
|
|
355
|
+
|
|
356
|
+
working:
|
|
357
|
+
actor: agent
|
|
358
|
+
prompt: |
|
|
359
|
+
The draft is accepted. Write COMMIT_MSG.md with one conventional-
|
|
360
|
+
commits message for the whole cycle, then finish your turn.
|
|
361
|
+
on:
|
|
362
|
+
"A COMMIT_MSG.md": done
|
|
363
|
+
"M COMMIT_MSG.md": done
|
|
364
|
+
|
|
365
|
+
done:
|
|
366
|
+
commit: '<%~ it.read("COMMIT_MSG.md") %>'
|
|
113
367
|
```
|
|
368
|
+
|
|
369
|
+
A clean `gtd step human` at `revising` (the `C` event) accepts the draft and
|
|
370
|
+
moves to `working`; any other pending change loops back to `drafting`. Writing
|
|
371
|
+
`COMMIT_MSG.md` at `working` enters `done`, which squashes every commit since
|
|
372
|
+
`idle` into one, using `COMMIT_MSG.md`'s content as the message, and discards
|
|
373
|
+
the file.
|
|
374
|
+
|
|
375
|
+
## Recipe: a per-task builder loop without a script state
|
|
376
|
+
|
|
377
|
+
A `picking`-style queue arbiter (see
|
|
378
|
+
[docs/examples/advanced-workflow.md](examples/advanced-workflow.md)) is a
|
|
379
|
+
`script` state that turns a task-queue glob into a diff patterns can see. A
|
|
380
|
+
driver that can't run scripts can get the same per-task loop out of pure
|
|
381
|
+
prompt/`on` declarations instead, at the cost of trusting the agent to honor a
|
|
382
|
+
marker protocol rather than a deterministic `ls`: see
|
|
383
|
+
[docs/design/work-packages.md §3](design/work-packages.md) ("Option B —
|
|
384
|
+
agent-encoded verdict") for the worked example.
|
|
385
|
+
|
|
386
|
+
## A fuller example: two-phase planning, task decomposition, agent-prepared review
|
|
387
|
+
|
|
388
|
+
The bundled default above is deliberately small. For a heavier machine — Q&A
|
|
389
|
+
planning loops, an architecture phase, task decomposition, the deterministic
|
|
390
|
+
per-task `picking` arbiter, and agent-prepared `.gtd/REVIEW.md` review — see
|
|
391
|
+
[docs/examples/advanced-workflow.md](examples/advanced-workflow.md), a
|
|
392
|
+
copy-paste-ready `.gtdrc` recipe.
|