@pmelab/gtd 2.8.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/README.md +61 -40
- package/bin/gtd-loop +43 -23
- package/dist/gtd.bundle.mjs +2281 -3486
- package/docs/cli.md +187 -288
- package/docs/configuration.md +335 -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/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 +6 -73
- package/docs/upgrading-from-v1.md +0 -45
- package/docs/workflow.md +0 -428
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Exploration: every rest is a prompt — checks as a scripted actor
|
|
2
|
+
|
|
3
|
+
> **Historical design record.** The actor-kind model this explores (v2's
|
|
4
|
+
> gates/counters, actor kinds) was deleted by the v3 rewrite; superseded by
|
|
5
|
+
> `docs/design/pattern-machine-plan.md` and [STATES.md](../../STATES.md).
|
|
6
|
+
|
|
7
|
+
> Companion to `configurable-state-machine.md` (its Appendix C's δ-purity
|
|
8
|
+
> program is now fully landed — see `delta-purity-plan.md`). This document
|
|
9
|
+
> explores the next generalization: **executed commands become emitted
|
|
10
|
+
> prompts**, run by the outer loop as shell scripts under a third actor kind,
|
|
11
|
+
> with their results entering the machine the same way every other actor's work
|
|
12
|
+
> does — as working-tree modifications captured on the next invocation.
|
|
13
|
+
|
|
14
|
+
## 1. The idea
|
|
15
|
+
|
|
16
|
+
Today the machine has exactly one place where it executes external code itself:
|
|
17
|
+
the check (`runTest` / `runHealthCheck` in `src/Events.ts`, backed by the
|
|
18
|
+
`TestRunner` service). Everything else an invocation does is git/fs bookkeeping.
|
|
19
|
+
The check is also the only transition that branches on something other than a
|
|
20
|
+
diff — an **exit code** — and the only reason `perform()` needs a subprocess
|
|
21
|
+
executor, timeout semantics, and output capture inside the `gtd` process.
|
|
22
|
+
|
|
23
|
+
The generalization: make the check an **actor**. A state that today fires
|
|
24
|
+
`runTest` instead _rests_, awaiting a `check` actor. `gtd next` emits that
|
|
25
|
+
state's prompt — which for a scripted actor is a **shell script** — and the
|
|
26
|
+
outer loop executes it, exactly as it feeds an agent prompt to an LLM or shows a
|
|
27
|
+
human prompt in a terminal. The script's effects land in the working tree
|
|
28
|
+
(`.gtd/FEEDBACK.md` on a red run, nothing on green), and the next
|
|
29
|
+
`gtd step check` captures them as the check's turn, with capture rules deciding
|
|
30
|
+
the label from the pending diff — the same δ(label, diff) discipline every other
|
|
31
|
+
actor already follows.
|
|
32
|
+
|
|
33
|
+
The end state: **every rest is a prompt state** — for a human, an agent, or a
|
|
34
|
+
shell. The machine performs only bookkeeping; every external effect (human
|
|
35
|
+
thought, LLM inference, command execution) happens outside the loop and enters
|
|
36
|
+
exclusively through the tree.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
rest ──gtd next──▶ prompt for the awaited actor
|
|
40
|
+
human → text in a terminal
|
|
41
|
+
agent → LLM prompt
|
|
42
|
+
check → shell script ◀── the new kind
|
|
43
|
+
actor acts (edits the tree, or doesn't)
|
|
44
|
+
──gtd step <actor>──▶ capture rules label the turn from the diff
|
|
45
|
+
──routing chains──▶ next rest
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 2. What executes where today (the residue)
|
|
49
|
+
|
|
50
|
+
| Effect | Where it runs today | Enters the machine as |
|
|
51
|
+
| ------------------------- | ------------------------------------- | ----------------------------------- |
|
|
52
|
+
| Human thought | outside (terminal) | pending diff → captured turn |
|
|
53
|
+
| Agent inference | outside (harness/LLM) | pending diff → captured turn |
|
|
54
|
+
| **Test / health command** | **inside `perform()` (`TestRunner`)** | **exit code → branch in `perform`** |
|
|
55
|
+
| Git/fs bookkeeping | inside `perform()` | commits (labels + trailers) |
|
|
56
|
+
|
|
57
|
+
The third row is the asymmetry this exploration removes. Note that Phase C
|
|
58
|
+
already moved the _decision_ to write time (the red/green/cap outcome is encoded
|
|
59
|
+
in the label the check writes) — but the _execution_ still lives inside the
|
|
60
|
+
invocation, which is why `gtd step agent` can block for the length of a test
|
|
61
|
+
suite, why `TestRunner` needs spawn-failure/timeout/empty-output handling
|
|
62
|
+
in-process, and why the `@inmem` e2e tier still spawns real subprocesses for
|
|
63
|
+
`testCommand`.
|
|
64
|
+
|
|
65
|
+
## 3. The model
|
|
66
|
+
|
|
67
|
+
### 3.1 A third actor kind: `scripted`
|
|
68
|
+
|
|
69
|
+
`ActorDef.kind` gains a third value alongside `interactive` and `autonomous`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
actors: [
|
|
73
|
+
{ name: "human", kind: "interactive" },
|
|
74
|
+
{ name: "agent", kind: "autonomous" },
|
|
75
|
+
{ name: "check", kind: "scripted" },
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The turn-taking engine keys on the kind, as it already does:
|
|
80
|
+
|
|
81
|
+
- **interactive** — dirty boundary tree is the entry turn; empty turns are
|
|
82
|
+
meaningful signals.
|
|
83
|
+
- **autonomous** — clean-tree steps at inert gates are no-ops; drafts are
|
|
84
|
+
structurally validated.
|
|
85
|
+
- **scripted** — its prompt is executable; its turn is _mechanical_ (the driver
|
|
86
|
+
runs the script verbatim and steps — there is no judgement in between). Its
|
|
87
|
+
empty turn is typically the green signal (an opt-in `empty` rule, as
|
|
88
|
+
everywhere else).
|
|
89
|
+
|
|
90
|
+
Nothing else in the engine changes: refusals, the fixpoint, and capture rules
|
|
91
|
+
are actor-generic already (the actor generalization landed earlier).
|
|
92
|
+
|
|
93
|
+
### 3.2 The prompt is the script — and gtd generates it
|
|
94
|
+
|
|
95
|
+
The state's prompt template for a scripted actor renders a **wrapper script**,
|
|
96
|
+
not the raw `testCommand`:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
#!/usr/bin/env bash
|
|
100
|
+
# gtd check turn — run verbatim, then: gtd step check
|
|
101
|
+
set +e
|
|
102
|
+
npm run test > .gtd/.check-output 2>&1
|
|
103
|
+
code=$?
|
|
104
|
+
if [ "$code" -ne 0 ]; then
|
|
105
|
+
mkdir -p .gtd
|
|
106
|
+
if [ -s .gtd/.check-output ]; then mv .gtd/.check-output .gtd/FEEDBACK.md
|
|
107
|
+
else echo "Test command \`npm run test\` failed with exit code $code and produced no output." > .gtd/FEEDBACK.md
|
|
108
|
+
fi
|
|
109
|
+
else
|
|
110
|
+
rm -f .gtd/.check-output
|
|
111
|
+
fi
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
gtd owns the wrapper's shape (output capture, the empty-failure sentinel, the
|
|
115
|
+
target file), templated from config at emit time — the driver stays dumb. This
|
|
116
|
+
keeps the machine the single author of check mechanics while moving the
|
|
117
|
+
_execution_ out.
|
|
118
|
+
|
|
119
|
+
### 3.3 Mechanics in the script, semantics at capture
|
|
120
|
+
|
|
121
|
+
A crucial split, learned from the Phase C work: the script must encode only
|
|
122
|
+
**mechanics** (did it exit non-zero; what was the output), never **semantics**
|
|
123
|
+
(is the budget spent; is this an escalation). Semantics belong in the capture
|
|
124
|
+
rules, decided from the pending diff plus the trailer vector at step time:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
testing: {
|
|
128
|
+
kind: "prompt",
|
|
129
|
+
awaits: "check",
|
|
130
|
+
prompts: { check: "@run-test" },
|
|
131
|
+
captureRules: [
|
|
132
|
+
// red at the cap — same file, different label; the escalated label's
|
|
133
|
+
// routing chain renames FEEDBACK.md → ERRORS.md as bookkeeping
|
|
134
|
+
{ when: (p) => p.feedbackPresent && p.counters.testFixCount >= p.fixAttemptCap,
|
|
135
|
+
label: "escalated", stamp: carry },
|
|
136
|
+
// red below the cap
|
|
137
|
+
{ when: (p) => p.feedbackPresent, label: "test-failed", stamp: tPlus1 },
|
|
138
|
+
// green — the outcome labels already decided at write time today,
|
|
139
|
+
// now decided at capture of the check's empty turn
|
|
140
|
+
{ empty: true, when: (p) => p.packagesPresent && !forceApprove(p), label: "agentic-review" },
|
|
141
|
+
{ empty: true, when: (p) => p.packagesPresent, label: "close-package-approved" },
|
|
142
|
+
{ empty: true, label: "tests-green", stamp: h0 },
|
|
143
|
+
],
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Why this split matters: if the script encoded `capReached` (as `perform()` does
|
|
148
|
+
today), a script emitted before the budget changed and executed after would
|
|
149
|
+
write the wrong file — a stale-script race with two sources of truth. With
|
|
150
|
+
semantics at capture, a stale script is harmless: the mechanics it performs
|
|
151
|
+
(write FEEDBACK.md with the output) are timeless, and the label — the part that
|
|
152
|
+
steers — is decided against the _current_ trailer when the turn is captured.
|
|
153
|
+
This is the δ discipline applied once more: the diff carries facts, the capture
|
|
154
|
+
encodes meaning.
|
|
155
|
+
|
|
156
|
+
### 3.4 Wire format
|
|
157
|
+
|
|
158
|
+
Check turns land as ordinary turn commits in the existing grammar:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
gtd(agent): building ← the build turn
|
|
162
|
+
gtd: testing ← routing: rest, awaiting check (label state today)
|
|
163
|
+
gtd(check): test-failed ← the check's captured turn (FEEDBACK.md in its diff)
|
|
164
|
+
|
|
165
|
+
Gtd-Counters: t=1 r=0 h=0
|
|
166
|
+
gtd(agent): fixing ← the fixer's turn
|
|
167
|
+
gtd: testing
|
|
168
|
+
gtd(check): agentic-review ← green, packages, threshold not reached
|
|
169
|
+
|
|
170
|
+
Gtd-Counters: t=1 r=0 h=0
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`git log --oneline` becomes a _complete_ trace: today the check outcome appears
|
|
174
|
+
as a bare machine label; under this model it is attributed — you can see that
|
|
175
|
+
the check actor ran, when, and what it concluded, exactly as for humans and
|
|
176
|
+
agents. Old histories remain inert boundaries (closed-set rule), and the trailer
|
|
177
|
+
scheme carries over unchanged.
|
|
178
|
+
|
|
179
|
+
## 4. What each current piece becomes
|
|
180
|
+
|
|
181
|
+
| Today | Under the scripted actor |
|
|
182
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
183
|
+
| `runTest` / `runHealthCheck` edge actions | deleted — `testing`/`health-check` become prompt states awaiting `check` |
|
|
184
|
+
| `TestRunner` service (spawn, timeout, output) | deleted from gtd; wrapper script + driver responsibility |
|
|
185
|
+
| exit-code branch in `perform()` | capture rules on the check state (diff + trailer) |
|
|
186
|
+
| `onGreen` decided at dispatch | green outcome decided at capture of the check's empty turn — same write moment |
|
|
187
|
+
| `capReached` baked into the action | capture-rule guard reading `p.counters` — no stale-script race |
|
|
188
|
+
| empty-failure sentinel in TS | templated into the wrapper script |
|
|
189
|
+
| `gtd step agent` blocks for the test suite | `gtd` returns instantly; the loop runs the script between invocations |
|
|
190
|
+
| `@inmem` e2e runs real `testCommand` subprocesses | scenarios write FEEDBACK.md by hand and `gtd step check` — fully in-memory |
|
|
191
|
+
| loop protocol: agent-only dispatch | uniform: read `gtd next --json`, dispatch on the awaited actor's kind |
|
|
192
|
+
| `testCommand` consumed by `TestRunner` | consumed by the prompt template (config → script text) |
|
|
193
|
+
|
|
194
|
+
And the generalization dividend: **any state can declare a scripted gate.** A
|
|
195
|
+
lint pass before review, a deploy verification after close-package, a schema
|
|
196
|
+
check between grilling and architecting — all become one `StateDef` with
|
|
197
|
+
`awaits: "check"` (or a second scripted actor with its own command), zero new
|
|
198
|
+
engine code. This is the missing piece of the original "completely configurable
|
|
199
|
+
state machine" goal: mechanical gates were the one transition type a user
|
|
200
|
+
couldn't add through configuration.
|
|
201
|
+
|
|
202
|
+
## 5. What this buys
|
|
203
|
+
|
|
204
|
+
1. **The machine's last impurity is gone.** `perform()` becomes pure git/fs
|
|
205
|
+
bookkeeping; the Effect graph loses `TestRunner` and `CommandExecutor`; the
|
|
206
|
+
always-clean invariant becomes structural (nothing inside gtd can block or
|
|
207
|
+
die mid-run with a half-written tree).
|
|
208
|
+
2. **`gtd` becomes instant and deterministic.** Every invocation is a pure
|
|
209
|
+
resolve + bookkeeping hop. All nondeterminism (LLM, human, subprocess) is
|
|
210
|
+
outside — which is also exactly what makes the property-test story and the
|
|
211
|
+
`@inmem` tier stronger.
|
|
212
|
+
3. **One protocol.** The outer loop stops special-casing: every iteration is
|
|
213
|
+
"ask `gtd next` who's up and what the prompt is; produce that actor's turn;
|
|
214
|
+
`gtd step <actor>`". Timeouts, retries, and sandboxing of the test command
|
|
215
|
+
move to the driver, which already owns them for the agent.
|
|
216
|
+
4. **Attributed history.** Check outcomes are turns with an author, a diff, and
|
|
217
|
+
a trailer — `git log` reads as a complete conversation between three actors.
|
|
218
|
+
5. **Configurable mechanical gates** (see §4) — without touching gtd.
|
|
219
|
+
|
|
220
|
+
## 6. Where it falls down
|
|
221
|
+
|
|
222
|
+
1. **It breaks the CLI contract — this is NOT behavior-preserving.** Every
|
|
223
|
+
driver and all ~40 feature files change shape: `gtd step agent` no longer
|
|
224
|
+
runs the tests; a check that used to be one invocation is now three
|
|
225
|
+
(`step agent` → run script → `step check`). Unlike the δ refactor, there is
|
|
226
|
+
no way to hide this behind the same e2e suite. It is a major version with a
|
|
227
|
+
migration story, not an internal refactor.
|
|
228
|
+
2. **Human ergonomics regress without a driver.** Today a human at `escalate`
|
|
229
|
+
runs `gtd step human` and the re-test happens. Under this model they must
|
|
230
|
+
copy-paste a script and run a second command. Mitigation: a thin optional
|
|
231
|
+
convenience — `gtd run` (or the loop skill) that executes the _emitted_ check
|
|
232
|
+
script verbatim and immediately steps the check actor. The machine still
|
|
233
|
+
never executes; the convenience wrapper does, behind an explicit opt-in
|
|
234
|
+
command whose only input is gtd's own emitted script. This preserves the
|
|
235
|
+
purity boundary while keeping the two-keystroke UX.
|
|
236
|
+
3. **The idle carve-out gets awkward.** Idle today awaits the human, and a human
|
|
237
|
+
step re-runs the health check inline. If idle awaits `check` instead, a bare
|
|
238
|
+
`gtd step human` at idle would be refused — hostile. Options: keep the idle
|
|
239
|
+
carve-out (a human step at idle _is_ the check turn's trigger via the
|
|
240
|
+
convenience wrapper), or split idle (rest for human) from a health-check
|
|
241
|
+
state (rest for check) that the human's empty idle turn chains into. Needs a
|
|
242
|
+
decision; neither is free.
|
|
243
|
+
4. **Generated bash is a liability.** Quoting the configured command, platform
|
|
244
|
+
variance (POSIX sh vs bash vs Windows), output-size limits — all move from
|
|
245
|
+
unit-tested TypeScript into templated script text that can only be tested by
|
|
246
|
+
executing it. The wrapper must stay tiny and boring; anything clever belongs
|
|
247
|
+
in capture rules.
|
|
248
|
+
5. **The prompt channel now carries executable code.** `testCommand` is already
|
|
249
|
+
arbitrary code execution by config, so the _trust_ is unchanged — but the
|
|
250
|
+
_transport_ changes: drivers must execute only the script emitted by
|
|
251
|
+
`gtd next` for a scripted actor, never script-looking content from
|
|
252
|
+
agent-authored files. The `--json` output should carry the actor kind and the
|
|
253
|
+
script in a dedicated field so drivers never have to parse prose.
|
|
254
|
+
6. **Two extra hops per check.** Negligible for an agent loop (the test run
|
|
255
|
+
dwarfs them), noticeable for scripted gates used as fast validations. The
|
|
256
|
+
fixpoint chaining could allow `step check` to be invoked by the driver
|
|
257
|
+
immediately after the script with no intervening `next`, keeping it to one
|
|
258
|
+
extra invocation.
|
|
259
|
+
7. **A non-cooperating driver can stall the machine.** Today a check cannot be
|
|
260
|
+
skipped: `step agent` runs it, period. Under this model a driver could keep
|
|
261
|
+
stepping other actors... except it can't — the rest awaits `check`, and every
|
|
262
|
+
other actor is refused out-of-turn. The stall is real (nothing runs the
|
|
263
|
+
script), but the safety property (nothing _bypasses_ the gate) is exactly as
|
|
264
|
+
strong as today's, enforced by the same refusal mechanism.
|
|
265
|
+
|
|
266
|
+
## 7. Verdict
|
|
267
|
+
|
|
268
|
+
This is the natural completion of the δ program: after Phase C, the check's
|
|
269
|
+
_decision_ already lives at write time and its _budget_ already rides on
|
|
270
|
+
trailers — the only thing still inside the machine is the subprocess itself.
|
|
271
|
+
Moving it out makes every rest a prompt and every actor a peer, at the cost of a
|
|
272
|
+
breaking CLI/protocol change and a real (but boundable) bash-templating
|
|
273
|
+
liability.
|
|
274
|
+
|
|
275
|
+
If pursued, the phasing writes itself:
|
|
276
|
+
|
|
277
|
+
- **Phase 1 — dual-mode:** add the `scripted` kind, the check states' capture
|
|
278
|
+
rules, and `gtd step check`; keep `runTest` as a deprecated inline path so the
|
|
279
|
+
existing e2e suite stays green while a parallel suite drives the new protocol.
|
|
280
|
+
- **Phase 2 — flip the default:** loop skill dispatches on actor kind; `gtd run`
|
|
281
|
+
convenience lands; feature files migrate scenario by scenario (each check hop
|
|
282
|
+
becomes script-run + step).
|
|
283
|
+
- **Phase 3 — delete:** `TestRunner`, `runTest`/`runHealthCheck`, and the
|
|
284
|
+
idle/testing carve-outs (per the idle decision taken in Phase 1).
|
|
285
|
+
|
|
286
|
+
The one decision worth taking _before_ any of this: §3.3's mechanics/semantics
|
|
287
|
+
split. Even today's inline check could be restructured so the label is decided
|
|
288
|
+
purely from (written file, trailer) — that is the piece that makes the model
|
|
289
|
+
safe, and it is adoptable independently of everything else here.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Design: state ↔ steering-file association (`file:` + `mode:`)
|
|
2
|
+
|
|
3
|
+
> Status: LANDED (2026-07-22). Goal: retain the v2 relationship between states
|
|
4
|
+
> and steering files — but as pure workflow CONFIGURATION that works for any
|
|
5
|
+
> state machine. Each state may declare one associated `file:` (an Eta template,
|
|
6
|
+
> so filenames live in `vars:` and are never repeated) and a `mode:` (`qa` |
|
|
7
|
+
> `review`) naming the file's FORMAT. The LSP stops hardcoding basenames: it
|
|
8
|
+
> reads the gtd config from the workspace (cwd/parent lookup, same cosmiconfig
|
|
9
|
+
> search the CLI uses), maps rendered file paths to modes, and regains the
|
|
10
|
+
> jump-to-relevant- file command — full v2 functionality, zero hardcoded
|
|
11
|
+
> workflow knowledge.
|
|
12
|
+
|
|
13
|
+
## 1. The two new state properties
|
|
14
|
+
|
|
15
|
+
| Property | Meaning |
|
|
16
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `file:` | Optional. THE steering file this state is about — the file a human/editor should look at while the machine rests here. An **Eta template** rendered with the standard `TemplateContext` (in practice `<%= it.vars.… %>`; the full context is available). Forbidden on commit states (never at rest), must render non-empty. |
|
|
18
|
+
| `mode:` | Optional, requires `file:`. The file's FORMAT, from a closed, documented vocabulary: `qa` (the open-questions format — `## Open Questions`, `###` question, `Suggested default:`/`Answer:`) or `review` (the checkbox review format — `# Review:` header, `##` chunks, `- [ ]` pointers). Unknown values are a load error (typos must not silently disable editor support). The ENGINE never branches on it — like `model`, it is emitted data; only the LSP interprets it. |
|
|
19
|
+
|
|
20
|
+
Multiple states may (and in the default, do) share one file+mode — the mode
|
|
21
|
+
describes the file, the association is per state.
|
|
22
|
+
|
|
23
|
+
**Emission:** `gtd next --json` and `gtd status --json` gain optional `file`
|
|
24
|
+
(rendered) and `mode` fields, omitted when unset (exactly like `model`); plain
|
|
25
|
+
`gtd status` prints `File:`/`Mode:` lines when set. This gives drivers and
|
|
26
|
+
editors the jump target without speaking LSP, and gives e2e an assertion
|
|
27
|
+
surface.
|
|
28
|
+
|
|
29
|
+
**Engine/compiler touch points:** `StateDef` gains `file?`/`mode?`;
|
|
30
|
+
`validateDefinition` gains the rules above (non-empty strings, mode ∈ {qa,
|
|
31
|
+
review}, mode requires file, both forbidden on commit states, errors
|
|
32
|
+
aggregated); `PatternConfig` compiles the two keys (KNOWN_STATE_KEYS, string
|
|
33
|
+
checks); `Edge`/`program` render `file:` at rest exactly where `model` is
|
|
34
|
+
rendered (a render failure errors like a model render failure).
|
|
35
|
+
|
|
36
|
+
## 2. The bundled default gets the association
|
|
37
|
+
|
|
38
|
+
New `vars:` entries (single source for the filenames):
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
vars:
|
|
42
|
+
testCommand: npm test
|
|
43
|
+
todoFile: .gtd/TODO.md
|
|
44
|
+
reviewFile: .gtd/REVIEW.md
|
|
45
|
+
feedbackFile: .gtd/FEEDBACK.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Per-state mapping (12 states):
|
|
49
|
+
|
|
50
|
+
| States | `file:` | `mode:` |
|
|
51
|
+
| ------------------------------------------------------------------- | ----------------------------- | ------------------------- |
|
|
52
|
+
| `grilling`, `todo-validating`, `grilling-answer`, `building` | `<%= it.vars.todoFile %>` | `qa` |
|
|
53
|
+
| `reviewing`, `review-validating`, `await-review`, `review-deciding` | `<%= it.vars.reviewFile %>` | `review` |
|
|
54
|
+
| `fixing`, `escalate` | `<%= it.vars.feedbackFile %>` | — (plain text, no format) |
|
|
55
|
+
| `idle`, `checking` | — | — |
|
|
56
|
+
|
|
57
|
+
Prompts and scripts that already name these files switch to the vars
|
|
58
|
+
(`<%~ it.vars.todoFile %>` etc.) so each filename has ONE source of truth in
|
|
59
|
+
templates. **Known limitation, documented:** `on` pattern keys are NOT Eta
|
|
60
|
+
templates — the default's patterns keep literal `.gtd/…` paths, so repointing a
|
|
61
|
+
filename var (`.gtdrc`/`GTD_VAR_`) without also overriding the workflow's
|
|
62
|
+
patterns desyncs the machine. The vars are a DRY mechanism inside templates and
|
|
63
|
+
the state↔file association, not a rename switch. (Making pattern keys var-aware
|
|
64
|
+
at compile time is noted as possible future work.)
|
|
65
|
+
|
|
66
|
+
The advanced example (`docs/examples/advanced-workflow.md`) gains the same
|
|
67
|
+
`file:`/`mode:` annotations (it is the LSP-heavy flow), including
|
|
68
|
+
`architectureFile` for its architecting phases (also `qa`-format).
|
|
69
|
+
|
|
70
|
+
## 3. The LSP becomes config-driven
|
|
71
|
+
|
|
72
|
+
- **Config discovery:** the server locates the gtd config by the SAME
|
|
73
|
+
cosmiconfig search the CLI uses, from the workspace root (initialize
|
|
74
|
+
`workspaceFolders`/`rootUri`), falling back to the open document's directory
|
|
75
|
+
walking upward. Reuse `ConfigService`'s loading/compiling — no second config
|
|
76
|
+
code path. Config is (re)loaded lazily per request (it is small); no watcher
|
|
77
|
+
needed for v1.
|
|
78
|
+
- **Path→mode dispatch:** compile the definition, render every state's `file:`
|
|
79
|
+
(vars-layer context; the LSP process's env supplies `GTD_VAR_` overrides like
|
|
80
|
+
any invocation), and build a map of absolute rendered paths → mode. Document
|
|
81
|
+
features dispatch on that map: `qa` files get question symbols + parser-error
|
|
82
|
+
diagnostics; `review` files get chunk/hunk symbols + check/uncheck code
|
|
83
|
+
actions + diagnostics. Conflicting modes for one path: first declaring state
|
|
84
|
+
wins, log a warning. **Fallback:** when NO config resolves (or a `.gtd` file
|
|
85
|
+
isn't mapped), keep today's basename dispatch (`TODO.md` → qa, `REVIEW.md` →
|
|
86
|
+
review) so the server still works standalone.
|
|
87
|
+
- **The jump command returns:** `gtd.openSteeringFile` — resolve the CURRENT
|
|
88
|
+
state exactly like the CLI (config + git HEAD via the existing Edge helpers;
|
|
89
|
+
this re-adds the git/config wiring the v2 server had), render its `file:`, and
|
|
90
|
+
`ShowDocumentRequest` it; a state with no `file:` yields an informational
|
|
91
|
+
message naming the state (v2 behavior). Registered as an executeCommand;
|
|
92
|
+
documented for editor keybinding.
|
|
93
|
+
|
|
94
|
+
## 4. Change chart
|
|
95
|
+
|
|
96
|
+
| Area | Change |
|
|
97
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
98
|
+
| `src/PatternMachine.ts` | `StateDef.file?/mode?` + validation rules (closed mode set). |
|
|
99
|
+
| `src/PatternConfig.ts` | Compile `file`/`mode` keys. |
|
|
100
|
+
| `src/Edge.ts` / `src/program.ts` | Render `file:` at rest; `file`/`mode` in both `--json` payloads + plain `status` lines. |
|
|
101
|
+
| `src/Lsp.ts` | Config discovery, path→mode dispatch (with basename fallback), `gtd.openSteeringFile`. |
|
|
102
|
+
| `src/workflows/default.yaml` | §2's vars + per-state `file:`/`mode:`; templates switch to filename vars. |
|
|
103
|
+
| Docs | STATES.md property table + §10; configuration.md (`file:`/`mode:` section + mode vocabulary + the pattern-literal limitation); cli.md JSON contracts; examples/advanced-workflow.md annotations; upgrading note (jump command is back, config-driven). |
|
|
104
|
+
| Tests | Unit: validation (mode vocabulary, mode-requires-file, commit-state prohibition, aggregation), compile, `file:` render + failure path; LSP path→mode dispatch, fallback, openSteeringFile (pure helpers). E2e: `driver-json-status.feature` file/mode emission (present + omitted); `lsp.feature` @live — documentSymbol served for a CUSTOM-named qa file mapped via a real `.gtdrc` (proving config-driven dispatch), and the jump command returning the rendered path at a known state. |
|
|
105
|
+
| Live verification | Scratch repo with a custom workflow whose `file:` uses a var: `gtd status` shows the rendered `File:`/`Mode:`; LSP smoke against the custom filename; default workflow spot-check (`grilling` → `File: .gtd/TODO.md`, `Mode: qa`). |
|
|
106
|
+
|
|
107
|
+
## 5. Out of scope
|
|
108
|
+
|
|
109
|
+
- Eta-rendering of `on` pattern keys (the rename-switch story) — future work,
|
|
110
|
+
noted in §2.
|
|
111
|
+
- New LSP modes beyond `qa`/`review` — the vocabulary can grow with new formats;
|
|
112
|
+
adding one means: parser (spec), validator recipe, LSP dispatch arm, docs.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Plan: steering-file validation loops in the default workflow
|
|
2
|
+
|
|
3
|
+
> Status: §2–§4 (the two validation loops in the default workflow) AND §5 (the
|
|
4
|
+
> LSP resurrection) both LANDED 2026-07-22. Goal: the bundled default workflow
|
|
5
|
+
> completely maps the functionality the deleted v2 LSP server (`src/Lsp.ts`,
|
|
6
|
+
> removed in the v3 rewrite) provided over the two steering files: the
|
|
7
|
+
> **open-questions format** in `.gtd/TODO.md` and the **checkbox review format**
|
|
8
|
+
> in `.gtd/REVIEW.md` — including a deterministic validation loop for each.
|
|
9
|
+
> Everything is pure workflow configuration; the engine is untouched.
|
|
10
|
+
|
|
11
|
+
## 1. The formats (authoritative spec = the deleted parsers)
|
|
12
|
+
|
|
13
|
+
Extract the exact rules from git history —
|
|
14
|
+
`git show 60c7490^:src/OpenQuestions.ts` and
|
|
15
|
+
`git show 60c7490^:src/ReviewDoc.ts` — and embed them BOTH in the agent prompts
|
|
16
|
+
(so drafts are born valid) and in the validator scripts (so malformed drafts
|
|
17
|
+
bounce):
|
|
18
|
+
|
|
19
|
+
**`.gtd/TODO.md` open questions** (`OpenQuestions.ts`):
|
|
20
|
+
|
|
21
|
+
- Free-form prose, plus an OPTIONAL `## Open Questions` section (absent = zero
|
|
22
|
+
questions, valid).
|
|
23
|
+
- Every `###` sub-heading directly under that section is one question; its
|
|
24
|
+
body's FIRST non-blank line must be `Suggested default: <text>` (agent's
|
|
25
|
+
unanswered default) or `Answer: <text>` (human's answer / folded-in) —
|
|
26
|
+
anything else is a format error.
|
|
27
|
+
- The section ends at the next level-1/2 heading or EOF.
|
|
28
|
+
|
|
29
|
+
**`.gtd/REVIEW.md`** (`ReviewDoc.ts`):
|
|
30
|
+
|
|
31
|
+
- First non-blank line: `# Review: <short-hash>`.
|
|
32
|
+
- A `<!-- base: <hash> -->` comment somewhere in the document.
|
|
33
|
+
- At least one `## <Chunk Title>` (non-empty title), each with at least one file
|
|
34
|
+
pointer matching `- [ ] ./path/to/file.ts#42 — note` / `- [x] …` (checkbox,
|
|
35
|
+
`./`-relative path, optional `#line`, optional `—`/`-` note) — the deleted
|
|
36
|
+
`FILE_POINTER_RE` is the shape to port.
|
|
37
|
+
|
|
38
|
+
## 2. The new default workflow (12 states)
|
|
39
|
+
|
|
40
|
+
Current 7 states, plus the two loops. `smart` on `grilling` and `reviewing`.
|
|
41
|
+
|
|
42
|
+
| State | Actor | Content | `on` (declaration order matters) |
|
|
43
|
+
| ------------------- | ----- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
44
|
+
| `idle` (initial) | human | message | `* **` → `grilling` |
|
|
45
|
+
| `grilling` | agent | prompt | `* **` → `todo-validating` |
|
|
46
|
+
| `todo-validating` | check | script | `A/M .gtd/FORMAT.md` → `grilling`; `D .gtd/FORMAT.md` → `grilling-answer`; `C` → `grilling-answer` |
|
|
47
|
+
| `grilling-answer` | human | message | `C` → `building`; `* **` → `grilling` |
|
|
48
|
+
| `building` | agent | prompt | `* **` → `checking` |
|
|
49
|
+
| `checking` | check | script | `A/M .gtd/FEEDBACK.md` → `fixing`; `D .gtd/FEEDBACK.md` → `reviewing`; `C` → `reviewing` |
|
|
50
|
+
| `fixing` (retry 3) | agent | prompt | `* **` → `checking` |
|
|
51
|
+
| `escalate` | human | message | `* **` → `checking` |
|
|
52
|
+
| `reviewing` | agent | prompt | `* **` → `review-validating` |
|
|
53
|
+
| `review-validating` | check | script | `A/M .gtd/FORMAT.md` → `reviewing`; `D .gtd/FORMAT.md` → `await-review`; `C` → `await-review` |
|
|
54
|
+
| `await-review` | human | message | `D .gtd/REVIEW.md` → `idle`; `M .gtd/REVIEW.md` → `review-deciding`; `* **` → `grilling`; (no `C` row — a clean step is a no-op, still awaiting) |
|
|
55
|
+
| `review-deciding` | check | script | `A/M .gtd/TODO.md` → `grilling`; `D .gtd/REVIEW.md` → `idle`; `C` → `await-review` (defensive) |
|
|
56
|
+
|
|
57
|
+
**Loop 1 — TODO.md questions (maps the LSP's question symbols/format):**
|
|
58
|
+
|
|
59
|
+
- `grilling` develops `.gtd/TODO.md` with the exact Open Questions format; if
|
|
60
|
+
`.gtd/FORMAT.md` exists, its prompt says to fix those findings first.
|
|
61
|
+
- `todo-validating` (deterministic script): parse-check TODO.md against §1's
|
|
62
|
+
rules. Malformed → write findings to `.gtd/FORMAT.md` (one line each) → back
|
|
63
|
+
to `grilling`. Valid → `rm -f .gtd/FORMAT.md` → `grilling-answer`.
|
|
64
|
+
- `grilling-answer` (human): answer by replacing `Suggested default:` with
|
|
65
|
+
`Answer:` in place; accept all remaining defaults with a clean step (`C` →
|
|
66
|
+
`building`); any edit loops back to `grilling` (the agent folds answers in,
|
|
67
|
+
possibly asks follow-ups, and re-validates).
|
|
68
|
+
|
|
69
|
+
**Loop 2 — REVIEW.md checkboxes (maps the LSP's review chunks/check actions):**
|
|
70
|
+
|
|
71
|
+
- Green `checking` now goes to `reviewing` (not straight to `await-review`).
|
|
72
|
+
- `reviewing` (agent, `model: smart`) writes `.gtd/REVIEW.md` in §1's exact
|
|
73
|
+
format — header hash from `<%= it.currentCommit %>` (short slice), base
|
|
74
|
+
comment from `<%= it.startCommit %>`, chunks grouped semantically over the
|
|
75
|
+
embedded `it.processDiff` (reuse the advanced example's reviewing prompt as
|
|
76
|
+
the base, tightened to the format spec).
|
|
77
|
+
- `review-validating` (deterministic script): structure check (header, base
|
|
78
|
+
comment, ≥1 chunk, every pointer matches the shape). Malformed →
|
|
79
|
+
`.gtd/FORMAT.md` → `reviewing`; valid → `await-review`.
|
|
80
|
+
- `await-review` (human): tick a pointer's checkbox to approve that item; when
|
|
81
|
+
EVERY box is ticked the cycle is approved. Add notes / untick / edit code for
|
|
82
|
+
feedback. Deleting `.gtd/REVIEW.md` outright is the power-user approve
|
|
83
|
+
shortcut. Any `M .gtd/REVIEW.md` step routes to the decider (declared before
|
|
84
|
+
`* **`, so a step that also touches code still goes to the decider); code-only
|
|
85
|
+
edits are feedback straight to `grilling`.
|
|
86
|
+
- `review-deciding` (deterministic script): if NO unticked `- [ ]` pointer
|
|
87
|
+
remains → approve: `rm .gtd/REVIEW.md` (→ `idle`). Otherwise → extract the
|
|
88
|
+
unticked pointers and their notes into a fresh `.gtd/TODO.md` (the next lap's
|
|
89
|
+
input) AND `rm .gtd/REVIEW.md` — the diff then contains both `A .gtd/TODO.md`
|
|
90
|
+
and `D .gtd/REVIEW.md`, and the `A/M .gtd/TODO.md` row is declared FIRST so
|
|
91
|
+
feedback wins (first-match declaration order is load-bearing here; pin it with
|
|
92
|
+
a test).
|
|
93
|
+
|
|
94
|
+
**Hygiene invariant (assert in e2e):** an approved cycle leaves `.gtd/` empty —
|
|
95
|
+
FEEDBACK.md is cleaned by green `checking`, FORMAT.md by a valid validation run,
|
|
96
|
+
REVIEW.md by the approval, TODO.md by `building`.
|
|
97
|
+
|
|
98
|
+
## 3. What the validators look like
|
|
99
|
+
|
|
100
|
+
Same discipline as `checking`/the old `picking`: mechanics-only bash, verdict =
|
|
101
|
+
file op, semantics = the `on` map. Grep/awk ports of the parser rules
|
|
102
|
+
(pragmatic, not a full markdown parser): e.g. a question block whose first
|
|
103
|
+
non-blank body line matches neither `^Answer:` nor `^Suggested default:` is a
|
|
104
|
+
finding; a pointer line failing the `- [(x| )] ./path(#N)?( — note)?` shape is a
|
|
105
|
+
finding; `- [ ]` presence is the review-deciding branch condition. Findings go
|
|
106
|
+
into `.gtd/FORMAT.md` verbatim (file + line + rule), so the fixing agent gets
|
|
107
|
+
actionable input.
|
|
108
|
+
|
|
109
|
+
## 4. Change chart
|
|
110
|
+
|
|
111
|
+
| Area | Change |
|
|
112
|
+
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `src/workflows/default.yaml` | +5 states (`todo-validating`, `grilling-answer`, `reviewing`, `review-validating`, `review-deciding`); `checking`'s green rows retarget `reviewing`; `await-review` rewritten (checkbox semantics, decider routing); prompts gain the format specs; header comment updated. |
|
|
114
|
+
| Engine / TypeScript | **No changes.** Everything is workflow data. |
|
|
115
|
+
| `STATES.md` §10 | New table + walkthrough (both loops, the declaration-order subtleties, the hygiene invariant). |
|
|
116
|
+
| `tests/integration/features/default-workflow.feature` | Rewritten: malformed-TODO lap (simulate `todo-validating` by writing `.gtd/FORMAT.md` + `gtd step check` — @inmem never executes scripts), valid draft → answer lap → accept; malformed-REVIEW lap; tick-all approve → `idle` with empty `.gtd/`; partial-tick feedback → TODO.md extraction → `grilling`; delete-shortcut approve; retry/escalation kept. |
|
|
117
|
+
| `smoke.feature` | Hop updates (`grilling` now routes to `todo-validating`). |
|
|
118
|
+
| Docs | `configuration.md` / `README.md` walkthrough mentions; `docs/examples/advanced-workflow.md` intro gains one line noting the default now carries the validation loops (the example itself is NOT reworked in this change — noted as a possible follow-up to rebase it on the new default). |
|
|
119
|
+
| Live verification | Real-script drive (`gtd run` at each validator) covering: agent writes malformed TODO → FORMAT.md → fixed lap; answers folded; malformed REVIEW lap; tick-all approve; partial-tick feedback extracting TODO.md; final tree `.gtd`-free at idle. |
|
|
120
|
+
|
|
121
|
+
## 5. The LSP server comes back (v3-shaped)
|
|
122
|
+
|
|
123
|
+
The v2 LSP (`src/Lsp.ts`, deleted at the v3 rewrite) is resurrected, scoped to
|
|
124
|
+
what is workflow-agnostic — the FILE FORMATS, not any particular state machine:
|
|
125
|
+
|
|
126
|
+
- **Restore the pure parsers verbatim from history** —
|
|
127
|
+
`git show 60c7490^:src/OpenQuestions.ts` / `:src/ReviewDoc.ts` (+ their unit
|
|
128
|
+
tests) — they are pure (no git/fs/Effect) and restore cleanly. They are the
|
|
129
|
+
executable spec of §1; the bash validators in §3 implement the SAME rules
|
|
130
|
+
(note this dual-implementation contract in both places — the parsers' unit
|
|
131
|
+
tests are the format's spec tests).
|
|
132
|
+
- **New `src/Lsp.ts`** (recover from history, then STRIP the v2-model
|
|
133
|
+
dependencies — Events/Machine/ReviewWindow/STATE_FILE are gone): keyed on FILE
|
|
134
|
+
NAME, not state. For `.gtd/TODO.md`: document symbols for open questions
|
|
135
|
+
([suggested]/[answered]). For `.gtd/REVIEW.md`: chunk/hunk symbols and the
|
|
136
|
+
check/uncheck code actions (hunk and whole-chunk). BOTH files: publish the
|
|
137
|
+
parsers' `errors` as diagnostics — the same findings the workflow's
|
|
138
|
+
`.gtd/FORMAT.md` validators produce, live in the editor. The v2
|
|
139
|
+
`gtd.openSteeringFile` command and its hardcoded state→file map are NOT
|
|
140
|
+
restored (a v3 workflow is arbitrary data; a state names no file) — noted as a
|
|
141
|
+
possible future addition driven by workflow config.
|
|
142
|
+
- **CLI**: re-add the `gtd lsp` subcommand (stdio transport, as before) to
|
|
143
|
+
`program.ts` dispatch + help + `docs/cli.md`; re-add the
|
|
144
|
+
`vscode-languageserver` / `vscode-languageserver-textdocument` dependencies;
|
|
145
|
+
confirm the tsdown single-file bundle still builds and the subcommand starts
|
|
146
|
+
from `dist/gtd.bundle.mjs`.
|
|
147
|
+
- **Tests**: parser unit tests restored; the LSP's pure helpers
|
|
148
|
+
(symbol/edit/diagnostic builders) unit-tested as in v2 (recover `Lsp.test.ts`
|
|
149
|
+
from history and adapt); restore/adapt the old protocol-level e2e
|
|
150
|
+
(`lsp.steps.ts` + feature) if it adapts cleanly, otherwise a minimal @live
|
|
151
|
+
smoke that `gtd lsp` starts and answers `initialize`.
|
|
152
|
+
- **Housekeeping**: add the restored pure modules to `stryker.config.json`'s
|
|
153
|
+
mutate list; `docs/development.md` + `docs/upgrading.md` notes (the LSP is
|
|
154
|
+
back, file-format-keyed); README one-liner on editor integration.
|
|
155
|
+
|
|
156
|
+
## 6. Explicitly out of scope
|
|
157
|
+
|
|
158
|
+
- `ARCHITECTURE.md` (the old second question phase) — the default keeps a single
|
|
159
|
+
grilling step; the advanced example still shows the two-phase shape.
|
|
160
|
+
- Engine-level validation of steering files — validation stays a workflow
|
|
161
|
+
concern, per the v3 charter; the LSP is editor tooling over the same formats,
|
|
162
|
+
not an engine hook.
|
|
163
|
+
- The `gtd.openSteeringFile` command (needs a state→file mapping that v3
|
|
164
|
+
workflows don't declare — future work, possibly a per-state `file:` hint).
|