@pmelab/gtd 8.0.0 → 8.2.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 +490 -110
- package/bin/gtd +104 -20
- package/dist/gtd.bundle.mjs +214 -102
- package/package.json +1 -2
- package/schema.json +33 -9
- package/docs/cli.md +0 -675
- package/docs/configuration.md +0 -994
- package/docs/design/configurable-state-machine.md +0 -1439
- package/docs/design/delta-purity-plan.md +0 -283
- package/docs/design/pattern-machine-plan.md +0 -164
- package/docs/design/pluggable-steering-modes.md +0 -154
- package/docs/design/scripted-actor.md +0 -289
- package/docs/design/state-file-association.md +0 -120
- package/docs/design/state-machine-validation.md +0 -437
- package/docs/design/steering-file-loops.md +0 -182
- package/docs/design/steering-file-validation-command.md +0 -385
- package/docs/design/work-packages.md +0 -193
- package/docs/development.md +0 -109
- package/docs/loop.md +0 -219
- package/docs/upgrading.md +0 -293
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
# Replacing the steering-file validation _states_ with a `gtd validate` command
|
|
2
|
-
|
|
3
|
-
> Status: IMPLEMENTED. The authoritative reference is now
|
|
4
|
-
> [STATES.md §12](../../STATES.md) (the model) and §10 (the 10-state bundled
|
|
5
|
-
> default); this doc is the design record. Supersedes the two validation-loop
|
|
6
|
-
> states in [steering-file-loops.md](steering-file-loops.md) §2–§4
|
|
7
|
-
> (`todo-validating`, `review-validating`); everything else that doc describes
|
|
8
|
-
> (the file formats §1, the LSP resurrection §5) stays.
|
|
9
|
-
>
|
|
10
|
-
> **As built — two decisions differ from the first draft of this plan:**
|
|
11
|
-
>
|
|
12
|
-
> 1. **No new state property.** A state is treated as "must hand over a valid
|
|
13
|
-
> file" purely by declaring both `file:` and `mode:` — there is no
|
|
14
|
-
> `validate:` flag and no `PatternMachine`/`PatternConfig`/`schema.json`
|
|
15
|
-
> change. This is the "Alternative considered" from §2.3, chosen for zero
|
|
16
|
-
> engine surface.
|
|
17
|
-
> 2. **`gtd next --json` is unchanged.** It emits no new field (it already
|
|
18
|
-
> carries `file`/`mode`); the driver simply runs `gtd validate` after every
|
|
19
|
-
> agent turn (a no-op, exit 0, when the state has nothing to validate). Only
|
|
20
|
-
> plain `gtd next` changed — it appends the self-validation instruction to a
|
|
21
|
-
> `prompt` rest that declares `file:`+`mode:`.
|
|
22
|
-
>
|
|
23
|
-
> **Follow-up refinement (also built):**
|
|
24
|
-
>
|
|
25
|
-
> 3. **`gtd validate` also formats**, and the check runs after a human edits
|
|
26
|
-
> too. `gtd validate` now formats the steering file in place (the
|
|
27
|
-
> `gtd format` markdown formatter) before parsing it. And `gtd step` runs the
|
|
28
|
-
> SAME format-and-validate as a capture gate (`enforceSteeringGate` in
|
|
29
|
-
> `src/program.ts`): committing a turn out of a state that declares
|
|
30
|
-
> `file:`+`mode:` formats+validates that file and REFUSES the step when it is
|
|
31
|
-
> invalid. That makes the check apply to a human's edit at a gate
|
|
32
|
-
> (`grilling-answer`, `await-review`) exactly as to an agent's draft, and a
|
|
33
|
-
> malformed steering file is never committed. An absent file (a deletion —
|
|
34
|
-
> `building`'s TODO.md, an `await-review` delete-to-approve) is a no-op, so
|
|
35
|
-
> those flows still pass. `gtd validate`'s earlier "missing file read as
|
|
36
|
-
> empty" behavior is dropped in favor of "absent → nothing to validate".
|
|
37
|
-
>
|
|
38
|
-
> The sections below are the original plan; read §2.1–§3.2 through these
|
|
39
|
-
> decisions (esp.: validate formats and is enforced at `gtd step`, and an absent
|
|
40
|
-
> file is a no-op rather than parsed-as-empty).
|
|
41
|
-
|
|
42
|
-
## 1. Motivation
|
|
43
|
-
|
|
44
|
-
The bundled default workflow (`src/workflows/default.yaml`) currently carries
|
|
45
|
-
two dedicated `check` states whose only job is to parse a steering file and
|
|
46
|
-
bounce a malformed draft back to its authoring agent via a `.gtd/FORMAT.md`
|
|
47
|
-
findings file:
|
|
48
|
-
|
|
49
|
-
- `todo-validating` — validates `.gtd/TODO.md`'s open-questions format after
|
|
50
|
-
`grilling`, before the human answers at `grilling-answer`.
|
|
51
|
-
- `review-validating` — validates `.gtd/REVIEW.md`'s checkbox format after
|
|
52
|
-
`reviewing`, before the human reviews at `await-review`.
|
|
53
|
-
|
|
54
|
-
This is noise in the state machine:
|
|
55
|
-
|
|
56
|
-
1. **2 of 12 states** exist only to re-derive a format check.
|
|
57
|
-
2. **A hand-synced dual implementation.** Each validator is a `grep/awk` port of
|
|
58
|
-
`src/OpenQuestions.ts` / `src/ReviewDoc.ts` — an explicit "executable spec ↔
|
|
59
|
-
bash validator" contract kept in sync _by hand_, with no shared code path. A
|
|
60
|
-
whole class of drift bugs.
|
|
61
|
-
3. **A third steering file** (`.gtd/FORMAT.md`) with its own lifecycle and
|
|
62
|
-
hygiene rules, plus "never touch FORMAT.md" / "fix FORMAT.md first" clauses
|
|
63
|
-
bolted onto the `grilling` and `reviewing` prompts.
|
|
64
|
-
4. **Two extra commits on every happy-path cycle** (`gtd(check): …` turns that
|
|
65
|
-
only say "the format was fine").
|
|
66
|
-
|
|
67
|
-
The core realization: the validation is redundant with tooling we already have.
|
|
68
|
-
`src/OpenQuestions.ts` / `src/ReviewDoc.ts` are pure parsers, and `src/Lsp.ts`
|
|
69
|
-
already publishes their `errors` as live editor diagnostics. What was missing
|
|
70
|
-
was a way for the **producing agent** to run the same canonical check itself,
|
|
71
|
-
mid-turn, so the human is only ever handed a _validated_ file.
|
|
72
|
-
|
|
73
|
-
## 2. The design
|
|
74
|
-
|
|
75
|
-
Validation stops being a **state** and becomes a **tool the producing agent uses
|
|
76
|
-
before it finishes its turn** (or that the driver runs on the agent's behalf).
|
|
77
|
-
No bash port, no `.gtd/FORMAT.md`, no extra states. The pure engine
|
|
78
|
-
(`src/PatternMachine.ts`) never validates a steering file — as today, that stays
|
|
79
|
-
out of scope for the engine (steering-file-loops.md §6). The only engine-shaped
|
|
80
|
-
addition is one declarative state property that marks _which_ states must hand
|
|
81
|
-
over a valid file (see §2.3).
|
|
82
|
-
|
|
83
|
-
Three parts:
|
|
84
|
-
|
|
85
|
-
### 2.1 A `gtd validate` command (current-state-scoped, canonical parsers)
|
|
86
|
-
|
|
87
|
-
A new subcommand that resolves the current state exactly like `gtd status`,
|
|
88
|
-
renders that state's `file:`, reads its working-tree contents, and runs the
|
|
89
|
-
parser its `mode:` selects:
|
|
90
|
-
|
|
91
|
-
- `mode: qa` → `parseOpenQuestions(content).errors`
|
|
92
|
-
- `mode: review` → `parseReviewDoc(content).errors`
|
|
93
|
-
|
|
94
|
-
Behavior:
|
|
95
|
-
|
|
96
|
-
- **Nothing to validate** — the resolved state declares no `file:` **or** no
|
|
97
|
-
`mode:` → exit 0, report "nothing to validate at \"\<state\>\"".
|
|
98
|
-
- **Valid** — the parser returns no `errors` → exit 0, report "\<file\>: valid".
|
|
99
|
-
- **Invalid** — the parser returns `errors` → **fail the Effect** with the
|
|
100
|
-
findings (one per line, each prefixed with the rendered file path), which
|
|
101
|
-
makes `main.ts` print `gtd: …` to stderr and exit non-zero. This non-zero exit
|
|
102
|
-
is the whole point: the producing agent (or the driver) loops
|
|
103
|
-
`write → gtd validate → fix` until it exits 0.
|
|
104
|
-
- A **missing** `file:` on disk is read as empty content (catch the read error,
|
|
105
|
-
treat as `""`), NOT as a hard error. For `qa` an empty/absent file is
|
|
106
|
-
trivially valid (no `## Open Questions` section); for `review` an empty file
|
|
107
|
-
fails the parser (no header, no chunks) — both are the correct outcomes.
|
|
108
|
-
Keeping validate purely a function of the parser over the file's content is
|
|
109
|
-
the crisp contract: **`gtd validate` fails ⟺ the file exists and violates its
|
|
110
|
-
format.**
|
|
111
|
-
|
|
112
|
-
`--json` shape: on success
|
|
113
|
-
`{ "state", "file"?, "mode"?, "valid": true, "errors": [] }`; on violations, the
|
|
114
|
-
existing error envelope (`{ "state": "error", "prompt": "<findings>" }`) via
|
|
115
|
-
`makeProgram`'s `--json` `catchAll` — consistent with how step refusals surface
|
|
116
|
-
under `--json`.
|
|
117
|
-
|
|
118
|
-
`gtd validate` takes no positional args (`rejectExtraArgs("validate", argv)`),
|
|
119
|
-
accepts `--json`, and — like every state command — goes through the repo-root
|
|
120
|
-
guard, auto-init, and the review-window close-first/re-arm-last bracketing.
|
|
121
|
-
|
|
122
|
-
### 2.2 Conditional self-validation instruction on `gtd next` (THE key rule)
|
|
123
|
-
|
|
124
|
-
The instruction that tells the agent to validate its own output is **not** a
|
|
125
|
-
static line in the YAML prompt. gtd appends it **only in plain (non-JSON)
|
|
126
|
-
`gtd next` output**; under `--json` it is withheld and the _driver_ owns the
|
|
127
|
-
validate-and-retry loop — mirroring how the driver already owns turn
|
|
128
|
-
orchestration.
|
|
129
|
-
|
|
130
|
-
- **`gtd next` (plain text):** when the resolved rest is a state that must hand
|
|
131
|
-
over a valid file (see §2.3), append a standard block to the rendered prompt,
|
|
132
|
-
referencing the rendered `file:`, e.g.:
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
<rendered prompt…>
|
|
136
|
-
|
|
137
|
-
Before finishing your turn, run `gtd validate` and fix every violation it
|
|
138
|
-
reports in <file> until it exits cleanly. Do not finish while it still
|
|
139
|
-
reports violations.
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
This is for a human (or a simple driver) who reads `gtd next` and hands the
|
|
143
|
-
text to an agent: the agent self-validates.
|
|
144
|
-
|
|
145
|
-
- **`gtd next --json`:** do **NOT** append the block. The `content` field stays
|
|
146
|
-
the clean rendered prompt. Instead emit the marker field `"validate": true`
|
|
147
|
-
(omitted when false — same discipline as `model`/`memory`/`file`/`mode`). The
|
|
148
|
-
driver reads this and, after the agent's turn but **before** `gtd step`, runs
|
|
149
|
-
`gtd validate`; on a non-zero exit it re-invokes the agent with the findings
|
|
150
|
-
and loops until `gtd validate` passes (subject to a cap — §2.5), then steps.
|
|
151
|
-
This is the JSON-mode equivalent of the appended self-instruction.
|
|
152
|
-
|
|
153
|
-
The appended text is gtd-owned (a constant in `program.ts`); it references the
|
|
154
|
-
already-rendered `rest.file`. It is emitted only by `runNextCommand`'s
|
|
155
|
-
plain-text branch — `gtd run` drives _script_ states (the `check` actor), never
|
|
156
|
-
prompt states, so it is unaffected.
|
|
157
|
-
|
|
158
|
-
### 2.3 A `validate: true` state property (the marker)
|
|
159
|
-
|
|
160
|
-
Add one optional boolean state property, `validate: true`, meaning: _the actor
|
|
161
|
-
at this state must hand over a `file:` that passes `gtd validate` (per its
|
|
162
|
-
`mode:`) before the turn is considered done._ Set it on `grilling` and
|
|
163
|
-
`reviewing` in the bundled default.
|
|
164
|
-
|
|
165
|
-
Why a dedicated property rather than keying off `mode:` presence: `building`
|
|
166
|
-
also declares `mode: qa` but its job is to **delete** `.gtd/TODO.md`, not
|
|
167
|
-
produce it — keying self-validation on `mode:` would wrongly target `building`.
|
|
168
|
-
`validate: true` names exactly the producing states.
|
|
169
|
-
|
|
170
|
-
Semantics / validation (in `validateDefinition`):
|
|
171
|
-
|
|
172
|
-
- `validate: true` **requires** both `file:` and `mode:` on the same state
|
|
173
|
-
(there is nothing to validate without a file and a format).
|
|
174
|
-
- **Forbidden on a commit state** (never at rest — same rule as
|
|
175
|
-
`reviewWindow`/`reviewBase`).
|
|
176
|
-
- Engine never branches on it at step time; it is edge/emission-only data, read
|
|
177
|
-
by `renderRest`/`gtd next` and surfaced in `--json`.
|
|
178
|
-
|
|
179
|
-
> **Alternative considered (no new property, lower engine risk):** skip
|
|
180
|
-
> `validate: true`; in plain `gtd next` append the block to any `prompt` state
|
|
181
|
-
> declaring `file:`+`mode:` (accepting a harmless no-op instruction on
|
|
182
|
-
> `building`, whose deleted TODO validates as empty-valid), and have the driver
|
|
183
|
-
> run `gtd validate` after _every_ agent turn (a no-op exit 0 whenever there is
|
|
184
|
-
> nothing to validate). This removes all `PatternMachine`/`PatternConfig`/schema
|
|
185
|
-
> changes. It is viable but less self-documenting (no `validate:true` in
|
|
186
|
-
> `--json` or `gtd status`, and the `building` prompt carries an odd line).
|
|
187
|
-
> **Recommended: the explicit property.** If the implementer prefers to avoid
|
|
188
|
-
> engine changes, the alternative is acceptable — pick one and keep it
|
|
189
|
-
> consistent across code, docs, and tests.
|
|
190
|
-
|
|
191
|
-
## 3. Concrete change list
|
|
192
|
-
|
|
193
|
-
### 3.1 New command — `src/program.ts`
|
|
194
|
-
|
|
195
|
-
- Import `parseOpenQuestions` (`./OpenQuestions.js`) and `parseReviewDoc`
|
|
196
|
-
(`./ReviewDoc.js`); `WorktreeReader` is already imported.
|
|
197
|
-
- Add `runValidateCommand(argv, json, write)`:
|
|
198
|
-
- `rejectExtraArgs("validate", argv)`.
|
|
199
|
-
- `git = yield* GitService`; `worktree = yield* WorktreeReader`.
|
|
200
|
-
- `{ rest, context } = yield* resolveRestContext(git)` (reuse the existing
|
|
201
|
-
helper — it builds the same context `gtd status`/`gtd next` use).
|
|
202
|
-
- `file = yield* renderFile(rest.stateDef, context)`;
|
|
203
|
-
`mode = rest.stateDef.mode`.
|
|
204
|
-
- No `file`/no `mode` → success "nothing to validate".
|
|
205
|
-
- Read `file` via `worktree.read`, catching a missing-file error into `""`.
|
|
206
|
-
- `errors = mode === "qa" ? parseOpenQuestions(content).errors : parseReviewDoc(content).errors`.
|
|
207
|
-
- Empty `errors` → success ("\<file\>: valid" / JSON `valid:true`).
|
|
208
|
-
- Non-empty →
|
|
209
|
-
`Effect.fail(new Error(<file> + " is not valid:\n" + errors.map(e => " - " + e).join("\n")))`.
|
|
210
|
-
- Add `"validate"` to `KNOWN_SUBCOMMANDS` and a `case "validate":` in
|
|
211
|
-
`dispatchKnownSubcommand`.
|
|
212
|
-
- Add the appended-instruction constant and wire it into `runNextCommand`'s
|
|
213
|
-
**plain** branch only (append when `rendered` is a prompt state with the
|
|
214
|
-
`validate` marker and a defined `rendered.file`); in the `--json` branch add
|
|
215
|
-
`...(rendered.validate ? { validate: true } : {})` to the emitted object and
|
|
216
|
-
do NOT touch `content`.
|
|
217
|
-
- Add the `validate` line to `HELP_TEXT`.
|
|
218
|
-
|
|
219
|
-
Exit-code note: failing the Effect is the mechanism for non-zero exit (see
|
|
220
|
-
`main.ts` `catchAll`). Findings land on stderr in plain mode, and under `prompt`
|
|
221
|
-
in the `--json` error envelope.
|
|
222
|
-
|
|
223
|
-
### 3.2 New state property — engine + compiler + emission
|
|
224
|
-
|
|
225
|
-
- `src/PatternMachine.ts`: add `validate?: boolean` to `StateDef`; in
|
|
226
|
-
`validateDefinition` add the two rules from §2.3 (requires `file`+`mode`;
|
|
227
|
-
forbidden on commit states).
|
|
228
|
-
- `src/PatternConfig.ts`: compile the `validate:` key (boolean; reject
|
|
229
|
-
non-boolean) in the per-state field compilers.
|
|
230
|
-
- `src/Edge.ts`: add `validate?: boolean` to `RenderedRest`; set it in
|
|
231
|
-
`renderRest` (`...(rest.stateDef.validate ? { validate: true } : {})`).
|
|
232
|
-
- `src/program.ts`: emit `validate` in `gtd next --json` (see §3.1) and,
|
|
233
|
-
optionally, in `gtd status --json` + a `Validate: yes` plain line for symmetry
|
|
234
|
-
with `model`/`memory`/`file`/`mode`.
|
|
235
|
-
- `schema.json`: add the `validate` boolean to the per-state schema.
|
|
236
|
-
|
|
237
|
-
### 3.3 `src/workflows/default.yaml`
|
|
238
|
-
|
|
239
|
-
- **Delete** the `todo-validating` and `review-validating` states.
|
|
240
|
-
- **Rewire**: `grilling.on: "* **": grilling-answer`;
|
|
241
|
-
`reviewing.on: "* **": await-review`.
|
|
242
|
-
- **Mark**: add `validate: true` to `grilling` and `reviewing` (they already
|
|
243
|
-
declare `file:`+`mode:`).
|
|
244
|
-
- **Prompts**: from `grilling` and `reviewing`, remove the `.gtd/FORMAT.md`
|
|
245
|
-
clauses ("If `.gtd/FORMAT.md` exists…", "Never touch `.gtd/FORMAT.md`…"). Do
|
|
246
|
-
NOT add a `gtd validate` line — gtd appends it in plain mode (§2.2). **Keep**
|
|
247
|
-
the embedded format spec (so drafts are born valid).
|
|
248
|
-
- **Header comment**: 12 → 10 states; drop the `todo-validating`/
|
|
249
|
-
`review-validating` cycle-order lines and rewrite Loop 1 / Loop 2 to describe
|
|
250
|
-
self-validation via `gtd validate`; remove `.gtd/FORMAT.md` from the hygiene
|
|
251
|
-
invariant (it no longer exists); update the "dual-implementation contract"
|
|
252
|
-
paragraph (there is no bash port anymore — `OpenQuestions.ts`/`ReviewDoc.ts`
|
|
253
|
-
are the single source of truth, consumed by the LSP and `gtd validate`).
|
|
254
|
-
|
|
255
|
-
Reachability after rewiring (assert mentally / via `validateDefinition`): all 10
|
|
256
|
-
states reachable —
|
|
257
|
-
`idle→grilling→grilling-answer→building→checking→(fixing| reviewing)`,
|
|
258
|
-
`fixing↔checking`, `fixing→escalate→checking`,
|
|
259
|
-
`reviewing→ await-review→(idle|review-deciding|grilling)`,
|
|
260
|
-
`review-deciding→(grilling|idle| await-review)`.
|
|
261
|
-
|
|
262
|
-
### 3.4 Driver contract — `skills/loop/SKILL.md` + `bin/gtd-loop`
|
|
263
|
-
|
|
264
|
-
Teach the reference driver the `--json` half of §2.2:
|
|
265
|
-
|
|
266
|
-
- After emitting an agent `prompt` turn whose `gtd next --json` carried
|
|
267
|
-
`"validate": true`, and after the agent has acted but **before**
|
|
268
|
-
`gtd step <actor>`, run `gtd validate`.
|
|
269
|
-
- On a non-zero exit, re-invoke the agent with a fix instruction that includes
|
|
270
|
-
the `gtd validate` findings, then run `gtd validate` again. Loop until it
|
|
271
|
-
passes.
|
|
272
|
-
- Cap the fix attempts (§2.5) and reuse the loop's existing no-progress / stall
|
|
273
|
-
handling to avoid spinning; surface a clear message on the cap.
|
|
274
|
-
- Then `gtd step <actor>` as today.
|
|
275
|
-
|
|
276
|
-
Document the same contract in `skills/loop/SKILL.md` (it is a genuine addition
|
|
277
|
-
to the driver protocol — `dispatch on kind` + the new validate gate).
|
|
278
|
-
|
|
279
|
-
### 3.5 Doc / comment sweep
|
|
280
|
-
|
|
281
|
-
- **`STATES.md`**: §1 property table — add the `validate` row. §10 — table 12 →
|
|
282
|
-
10 states (drop the two validator rows; update `grilling`/`reviewing` `on`;
|
|
283
|
-
note `validate: true` on `grilling`/`reviewing`); rewrite the Loop 1 / Loop 2
|
|
284
|
-
walkthrough; drop `.gtd/FORMAT.md` from the hygiene invariant.
|
|
285
|
-
- **`docs/cli.md`**: document `gtd validate` (and the `--json` shape).
|
|
286
|
-
- **`docs/configuration.md`**: document the `validate:` property; remove the
|
|
287
|
-
FORMAT-loop description; keep the `file:`/`mode:` known-limitation.
|
|
288
|
-
- **`docs/design/steering-file-loops.md`**: add a status banner at the top
|
|
289
|
-
pointing here and noting §2–§4's validator states were replaced by
|
|
290
|
-
`gtd validate` + the `validate:` marker (leave §1 formats and §5 LSP intact).
|
|
291
|
-
- **`docs/upgrading.md`**, **`docs/development.md`**,
|
|
292
|
-
**`docs/design/state-file-association.md`**,
|
|
293
|
-
**`docs/design/state-machine-validation.md`**: replace `todo-validating`/
|
|
294
|
-
`review-validating`/`.gtd/FORMAT.md` references with the `gtd validate` model.
|
|
295
|
-
- **`README.md`**: adjust the steering-file / editor-integration mention.
|
|
296
|
-
- **`AGENTS.md`**: update the "Changing the Workflow" e2e feature list and the
|
|
297
|
-
"Scripted Check Actor" / dual-implementation notes to reflect that the bundled
|
|
298
|
-
default no longer ships the two validator states (the `check` actor still
|
|
299
|
-
exists for `checking`/`review-deciding`).
|
|
300
|
-
- **`src/OpenQuestions.ts` / `src/ReviewDoc.ts`** docstrings: remove the
|
|
301
|
-
"executable spec ↔ bash validator contract" paragraph and the stale
|
|
302
|
-
`gtd questions` / `gtd changesets` command references; state they are now the
|
|
303
|
-
single source of truth consumed by the LSP (`src/Lsp.ts`) and `gtd validate`.
|
|
304
|
-
- **`src/Lsp.ts`** docstrings: the two "same findings the workflow's
|
|
305
|
-
`todo-validating`/`review-validating` script would write to `.gtd/FORMAT.md`"
|
|
306
|
-
comments → reference `gtd validate` instead.
|
|
307
|
-
|
|
308
|
-
### 3.6 Tests
|
|
309
|
-
|
|
310
|
-
- **`tests/integration/features/smoke.feature`**: happy path becomes
|
|
311
|
-
`idle → grilling → grilling-answer → building → checking` (drop the
|
|
312
|
-
`todo-validating` hop and its `gtd step check`).
|
|
313
|
-
- **`tests/integration/features/default-workflow.feature`**: major rewrite.
|
|
314
|
-
Remove the `.gtd/FORMAT.md` malformed-lap simulations from the main journey
|
|
315
|
-
and the two standalone "malformed … bounces back" scenarios (that machinery is
|
|
316
|
-
gone). New hops: `grilling → grilling-answer` and `reviewing → await-review`
|
|
317
|
-
directly. The `.gtd/FORMAT.md does not exist` hygiene assertion can stay
|
|
318
|
-
(trivially true); update the comment that attributed its cleanup to the
|
|
319
|
-
validators.
|
|
320
|
-
- **`tests/integration/features/mermaid.feature`**: drop the
|
|
321
|
-
`state "todo-validating" as todo_validating` assertion (no longer a default
|
|
322
|
-
state); keep the rest.
|
|
323
|
-
- **`src/Mermaid.test.ts`**: the hyphenated-name unit test uses
|
|
324
|
-
`"todo-validating"` as a _synthetic_ fixture name — fine to leave, it is not
|
|
325
|
-
the bundled default. The "renders the bundled default … covering every state"
|
|
326
|
-
test iterates actual states and needs no change.
|
|
327
|
-
- **New `tests/integration/features/validate.feature` (@inmem)** — cucumber
|
|
328
|
-
scenarios for the new feature (per `AGENTS.md`: one scenario per feature,
|
|
329
|
-
composable `Given` steps, real file content in the scenario text). `@inmem` is
|
|
330
|
-
fine: `gtd validate` reads the working tree and runs a pure parser — no script
|
|
331
|
-
execution. Cover, with HEAD placed at the relevant state via
|
|
332
|
-
`a commit "gtd(<actor>): <state>" that adds …`:
|
|
333
|
-
- `grilling` + a valid `.gtd/TODO.md` → `gtd validate` succeeds, stdout
|
|
334
|
-
"valid".
|
|
335
|
-
- `grilling` + a malformed `.gtd/TODO.md` (a bare `###` question heading with
|
|
336
|
-
no question text) → `gtd validate` fails, stderr carries the finding.
|
|
337
|
-
- `reviewing` + a malformed `.gtd/REVIEW.md` (missing `# Review:` header) →
|
|
338
|
-
fails with the finding; a valid one → succeeds.
|
|
339
|
-
- a state with a `file:` but no `mode:` (e.g. `fixing`) → succeeds with
|
|
340
|
-
"nothing to validate".
|
|
341
|
-
- `gtd next` at `grilling` (plain) **contains** the appended validate
|
|
342
|
-
instruction; `gtd next --json` at `grilling` **does not** contain it in
|
|
343
|
-
`content` and **does** emit `"validate":true`.
|
|
344
|
-
- **Optional (nice-to-have) `@live`**: a `gtd-loop`-style scenario proving the
|
|
345
|
-
driver runs `gtd validate` after a producing agent turn and re-runs the agent
|
|
346
|
-
on findings before stepping. If added, follow `gtd-loop.feature`'s stub-agent
|
|
347
|
-
pattern. May be deferred if the driver-contract change is landed with unit
|
|
348
|
-
coverage instead.
|
|
349
|
-
|
|
350
|
-
### 3.7 No change needed
|
|
351
|
-
|
|
352
|
-
- `stryker.config.json` — the parsers stay in the mutate list; nothing removed.
|
|
353
|
-
- `src/PatternTemplates.ts` — the append happens in `program.ts`, not in
|
|
354
|
-
template rendering.
|
|
355
|
-
|
|
356
|
-
## 4. Acceptance checklist
|
|
357
|
-
|
|
358
|
-
- [ ] `gtd validate` exists, is current-state-scoped, exits non-zero with
|
|
359
|
-
findings on a malformed file and 0 otherwise; `--json` shape as in §2.1.
|
|
360
|
-
- [ ] `gtd next` (plain) appends the self-validation instruction at
|
|
361
|
-
`grilling`/`reviewing`; `gtd next --json` withholds it and emits
|
|
362
|
-
`"validate": true`.
|
|
363
|
-
- [ ] `validate:` property compiles, validates (requires `file`+`mode`,
|
|
364
|
-
forbidden on commit states), and is emitted in `--json`.
|
|
365
|
-
- [ ] The bundled default is 10 states; `todo-validating`/`review-validating`/
|
|
366
|
-
`.gtd/FORMAT.md` are gone; an approved cycle still leaves `.gtd/` empty.
|
|
367
|
-
- [ ] The reference driver runs `gtd validate` after a `validate:true` agent
|
|
368
|
-
turn and loops the agent on findings before stepping (capped).
|
|
369
|
-
- [ ] `npm test` green; `npm run build` (single-file bundle) green; docs and
|
|
370
|
-
`AGENTS.md` updated. (Do NOT run `npm run test:mutation`.)
|
|
371
|
-
|
|
372
|
-
## 5. Notes for the implementer
|
|
373
|
-
|
|
374
|
-
- Keep CLI flags orthogonal (`AGENTS.md`): `gtd validate` accepts only `--json`;
|
|
375
|
-
reject unknown `--` options (the existing `unknownOption` guard already covers
|
|
376
|
-
this once `validate` is a known subcommand).
|
|
377
|
-
- `gtd validate` resolves the CURRENT state — during a producing agent's turn,
|
|
378
|
-
HEAD still points at the commit that entered `grilling`/`reviewing` and the
|
|
379
|
-
agent's file is uncommitted in the working tree, so resolution +
|
|
380
|
-
`worktree.read` see exactly the right thing. No `gtd step` has happened yet.
|
|
381
|
-
- The `file:`/`mode:` known-limitation (STATES.md §10, configuration.md) still
|
|
382
|
-
applies: `on` pattern keys are literal `.gtd/...` paths. `gtd validate` reads
|
|
383
|
-
the _rendered_ `file:`, so repointing `todoFile`/`reviewFile` via `vars:`
|
|
384
|
-
keeps validate correct even though the (now-removed) `on` FORMAT rows used to
|
|
385
|
-
desync.
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# Design: the builder / work-packages flow as pure workflow configuration
|
|
2
|
-
|
|
3
|
-
> Status: LANDED — Option A shipped in the bundled default workflow's `picking`
|
|
4
|
-
> state. It now lives in the bundled **unified** template's advanced flow (the
|
|
5
|
-
> `picking` state, reached by starting a cycle with `.gtd/REQUIREMENTS.md` — see
|
|
6
|
-
> [STATES.md §10](../../STATES.md#10-the-bundled-workflow-templates)). The rest
|
|
7
|
-
> of this document is kept as the decision record: why Option A was chosen over
|
|
8
|
-
> B/C/D, and the process-per-task topology (§6) remains a documented,
|
|
9
|
-
> unimplemented recipe.
|
|
10
|
-
>
|
|
11
|
-
> Context (as originally written): v2 had an engine-level package loop
|
|
12
|
-
> (`hasPackages` / `packagesRemaining` guards, the `close-package` label,
|
|
13
|
-
> `removePackageDir`). v3 deleted all of it; the bundled default's `building`
|
|
14
|
-
> state used to implement the entire task set in ONE monolithic agent turn. This
|
|
15
|
-
> doc plans how to get the incremental task-by-task loop back **without the
|
|
16
|
-
> engine ever learning what a "package" is** — it stays a concern of the
|
|
17
|
-
> workflow configuration.
|
|
18
|
-
|
|
19
|
-
## 1. What the loop actually needs
|
|
20
|
-
|
|
21
|
-
The per-task cycle is: _pick the next task → implement it → check → (fix loop) →
|
|
22
|
-
close the task → if tasks remain, loop; else review_. Decomposed into v3
|
|
23
|
-
vocabulary:
|
|
24
|
-
|
|
25
|
-
| Need | Already expressible? |
|
|
26
|
-
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
-
| Task queue | Yes — files under a glob (e.g. `.gtd/tasks/NN-name.md`), written by `decompose`. |
|
|
28
|
-
| "Close a task" | Yes — the actor deletes the task file; the diff carries `D .gtd/tasks/…`. |
|
|
29
|
-
| Per-task check + fix loop | Yes — the existing `checking`/`fixing` shape, unchanged. |
|
|
30
|
-
| Feed ONE task into the prompt | Mostly — a file naming the current task can be `it.read()` into the building prompt. |
|
|
31
|
-
| **The exit branch: "none left?"** | **No.** Patterns match the pending **diff**; deleting the _last_ task file produces the same diff shape as any other (`D .gtd/tasks/…`). Emptiness of a glob is a **tree** property, invisible to the grammar. |
|
|
32
|
-
|
|
33
|
-
So the whole design question is: who decides "the queue is empty", and how does
|
|
34
|
-
that decision become a diff the patterns can see? Everything below is an answer
|
|
35
|
-
to only that.
|
|
36
|
-
|
|
37
|
-
## 2. Option A — scripted arbiter state (pure config, deterministic) — RECOMMENDED
|
|
38
|
-
|
|
39
|
-
Apply the `checking` discipline to the queue itself: a `script` state whose only
|
|
40
|
-
job is to inspect the tree (which patterns can't) and leave a file-op verdict
|
|
41
|
-
(which patterns can). Mechanics in the script, semantics in the `on` map — no
|
|
42
|
-
trust in an agent, no engine change.
|
|
43
|
-
|
|
44
|
-
```yaml
|
|
45
|
-
decompose:
|
|
46
|
-
actor: agent
|
|
47
|
-
prompt: |
|
|
48
|
-
… write ordered task files under .gtd/tasks/ …
|
|
49
|
-
on:
|
|
50
|
-
"* .gtd/tasks/**": picking
|
|
51
|
-
|
|
52
|
-
picking: # the deterministic queue arbiter
|
|
53
|
-
actor: check
|
|
54
|
-
script: |
|
|
55
|
-
next=$(ls .gtd/tasks/*.md 2>/dev/null | head -n 1)
|
|
56
|
-
if [ -n "$next" ]; then
|
|
57
|
-
printf '%s' "$next" > .gtd/NEXT.md
|
|
58
|
-
else
|
|
59
|
-
rm -f .gtd/NEXT.md
|
|
60
|
-
fi
|
|
61
|
-
on:
|
|
62
|
-
"D .gtd/NEXT.md": reviewing # queue just emptied — MUST precede the `*` row
|
|
63
|
-
"* .gtd/NEXT.md": building # queue non-empty (A first time, M after)
|
|
64
|
-
"C": reviewing # queue empty and NEXT.md never existed
|
|
65
|
-
|
|
66
|
-
building:
|
|
67
|
-
actor: agent
|
|
68
|
-
prompt: |
|
|
69
|
-
Implement exactly ONE task: <%~ it.read(".gtd/NEXT.md") %> — read that
|
|
70
|
-
file, do the work, delete the task file when done. Touch nothing else
|
|
71
|
-
under .gtd/.
|
|
72
|
-
on:
|
|
73
|
-
"* **": checking
|
|
74
|
-
|
|
75
|
-
checking:
|
|
76
|
-
# unchanged, except green loops back to the arbiter:
|
|
77
|
-
on:
|
|
78
|
-
"A .gtd/FEEDBACK.md": fixing
|
|
79
|
-
"M .gtd/FEEDBACK.md": fixing
|
|
80
|
-
"C": picking
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Properties:
|
|
84
|
-
|
|
85
|
-
- **Deterministic.** The branch is a shell `ls`, not an agent's memory. Every
|
|
86
|
-
verdict lands in the commit history as an ordinary `gtd(check): <state>` turn.
|
|
87
|
-
- **`NEXT.md` is both the verdict and the context** — the building prompt
|
|
88
|
-
interpolates it, so each building turn sees exactly one task.
|
|
89
|
-
- The three `on` rows are total over the script's possible outcomes
|
|
90
|
-
(write/overwrite, delete, no-op) — no refusal path.
|
|
91
|
-
- Cost: one extra state and one extra driver round-trip per task (same cost
|
|
92
|
-
`checking` already pays; the loop already executes scripts via `gtd run`).
|
|
93
|
-
|
|
94
|
-
## 3. Option B — agent-encoded verdict (pure config, zero extra states)
|
|
95
|
-
|
|
96
|
-
The v3 verdict discipline says branch outcomes are encoded by _which files the
|
|
97
|
-
actor writes or deletes_ (`D FEEDBACK.md` = approve). Apply it here: `decompose`
|
|
98
|
-
also writes a marker `.gtd/tasks/OPEN`; the building/closing prompt instructs:
|
|
99
|
-
_implement the next task and delete its file; if it was the last one, also
|
|
100
|
-
delete `.gtd/tasks/OPEN`_. Declaration order does the rest:
|
|
101
|
-
|
|
102
|
-
```yaml
|
|
103
|
-
building:
|
|
104
|
-
on:
|
|
105
|
-
"D .gtd/tasks/OPEN": reviewing # declared first — wins when present
|
|
106
|
-
"* **": checking
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
- Pro: no extra state, no script execution needed (works for drivers that only
|
|
110
|
-
handle prompt/message states).
|
|
111
|
-
- Con: correctness rests on the agent honouring the marker protocol. The failure
|
|
112
|
-
mode is soft (a forgotten marker just means one more "none left, delete the
|
|
113
|
-
marker" turn), but the branch is not deterministic and never appears as a
|
|
114
|
-
machine-authored verdict.
|
|
115
|
-
|
|
116
|
-
Documented as the zero-infrastructure variant; A is preferred wherever the
|
|
117
|
-
driver can run scripts.
|
|
118
|
-
|
|
119
|
-
## 4. Option C — generic engine helper: tree-predicate guards on patterns
|
|
120
|
-
|
|
121
|
-
The one candidate engine change, if the arbiter round-trip ever proves too
|
|
122
|
-
heavy. The grammar gains an optional **tree predicate** appended to a pattern:
|
|
123
|
-
`<event> & N <glob>` ("and no file matching glob exists in the tree after the
|
|
124
|
-
pending changes") / `& E <glob>` ("and at least one exists"):
|
|
125
|
-
|
|
126
|
-
```yaml
|
|
127
|
-
building:
|
|
128
|
-
on:
|
|
129
|
-
"D .gtd/tasks/** & N .gtd/tasks/**": reviewing # closed the LAST task
|
|
130
|
-
"D .gtd/tasks/**": picking # closed a task, more remain
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
- Still fully agnostic: the engine learns _glob emptiness_, never "packages".
|
|
134
|
-
- δ-purity holds in the same sense as today: the decision is a function of (HEAD
|
|
135
|
-
tree + pending diff); the tree listing is already available at the edge
|
|
136
|
-
(`changedPaths` / `WorktreeReader`).
|
|
137
|
-
- The `&` form keeps rows **event-triggered** (a bare `N …` row that could fire
|
|
138
|
-
on any unrelated diff would be a foot-gun); the predicate only guards.
|
|
139
|
-
- Cost: pattern parser/matcher/validation growth, resolve needs a tree listing,
|
|
140
|
-
STATES.md §3 grows a second concept, property tests. Deliberately **deferred**
|
|
141
|
-
— Option A covers the need with zero engine surface; this only pays for itself
|
|
142
|
-
if arbiter states show up in every workflow and feel like boilerplate.
|
|
143
|
-
|
|
144
|
-
## 5. Option D — template enumeration helper: `it.ls(glob)`
|
|
145
|
-
|
|
146
|
-
Small, orthogonal, generic: add `ls(glob) → string[]` (sorted, working-tree)
|
|
147
|
-
next to `read(path)` in the template context.
|
|
148
|
-
|
|
149
|
-
- Lets Option A's script shrink to pure verdict-writing and lets prompts and
|
|
150
|
-
messages show the queue ("Remaining tasks:\n<%=
|
|
151
|
-
it.ls('.gtd/tasks/*.md').join('\n') %>") without shell gymnastics.
|
|
152
|
-
- Lets Option B's prompt state the ground truth instead of trusting agent memory
|
|
153
|
-
("these tasks remain: …; if this list has one entry, also delete the marker").
|
|
154
|
-
- No new decision power (templates render content, they don't route), so it
|
|
155
|
-
cannot compromise the δ discipline.
|
|
156
|
-
|
|
157
|
-
## 6. A topology variant worth knowing: process-per-task
|
|
158
|
-
|
|
159
|
-
Nothing stops a workflow from making **each task its own process**: the per-task
|
|
160
|
-
cycle ends in its own `commit:` state, squashing just that task's work into one
|
|
161
|
-
commit; the squash boundary resolves back to the initial state, whose router (an
|
|
162
|
-
Option-A-style arbiter at or right after `initial`) picks the next task or falls
|
|
163
|
-
through to the review/idle path.
|
|
164
|
-
|
|
165
|
-
Why it's attractive:
|
|
166
|
-
|
|
167
|
-
- **Per-task commits** — history reads one commit per task instead of one per
|
|
168
|
-
cycle.
|
|
169
|
-
- **Per-task budgets** — `retry` visit-counts are process-scoped, so today a
|
|
170
|
-
10-task cycle POOLS `fixing`'s `max: 3` across all tasks; task-sized processes
|
|
171
|
-
give each task its own cap. (This interaction exists regardless of which
|
|
172
|
-
option is chosen and should be called out in docs.)
|
|
173
|
-
- Smaller diffs at review time.
|
|
174
|
-
|
|
175
|
-
Why it's not the default recommendation: the initial state doubles as the human
|
|
176
|
-
entry point, so the router shape is more intricate (the arbiter must distinguish
|
|
177
|
-
"mid-flight, tasks remain" from "fresh repo, wait for a human"), and cycle-level
|
|
178
|
-
review/squash semantics change meaningfully. A worked example belongs in
|
|
179
|
-
`docs/configuration.md` once the basic loop lands.
|
|
180
|
-
|
|
181
|
-
## 7. Recommendation
|
|
182
|
-
|
|
183
|
-
1. **Now (config-only, no engine change):** re-author the bundled default's
|
|
184
|
-
builder segment as the Option-A loop
|
|
185
|
-
(`decompose → picking ⇄ building ⇄ checking/fixing → reviewing`), with the
|
|
186
|
-
`NEXT.md` arbiter script and the one-task building prompt. Document Option B
|
|
187
|
-
in `docs/configuration.md` as the script-less variant.
|
|
188
|
-
2. **Now (one small generic helper):** add `it.ls(glob)` (Option D) — cheap,
|
|
189
|
-
broadly useful, and it makes both A and B materially better.
|
|
190
|
-
3. **Deferred:** Option C (`& N/E <glob>` tree-predicate guards) — revisit only
|
|
191
|
-
if arbiter boilerplate becomes a demonstrated pain across real workflows.
|
|
192
|
-
4. **Docs either way:** call out the retry-cap pooling interaction (§6) and the
|
|
193
|
-
process-per-task topology as a recipe.
|